Files
ReelShort/test/playback/playback_session_test.dart
T
Vicky 0e5d550b50 test(client): 用例名统一为 should_<期望>_when_<条件>(155/155)
为什么:红线第 8 条门禁立即生效,要求 Flutter 测试命名合规;此前 155 条用例名是英文自然语言
或中文描述,合规数 0/155,属存量欠账,按"先补齐再上新功能"在本批处理。

- 25 个测试文件、155 条用例改名,按「文件 + 旧名」精确定位,每条断言命中恰好 1 次才替换
- 只改名字字面量:改动行 155 删 + 155 增,无一行不含字符串字面量(断言与用例体零改动)
- 门禁:dart analyze lib test = No issues found;flutter test = 155 全过
2026-10-16 09:41:14 +08:00

216 lines
6.5 KiB
Dart

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:nontonki/core/data/models/drama.dart';
import 'package:nontonki/core/data/services/playback_session.dart';
import 'package:nontonki/core/data/models/player_port.dart';
void main() {
final episodes = List.generate(
4,
(i) => Episode(
id: 'ep-${i + 1}',
number: i + 1,
title: 'Episode ${i + 1}',
videoUrl: 'https://example.test/episode_0${i + 1}.mp4',
isFree: i < 2,
),
);
test('should_create_no_player_when_episode_is_locked', () async {
final factory = FakeFactory();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
await session.select(2);
expect(session.isLocked, isTrue);
expect(factory.created, isEmpty);
await session.close();
});
test('should_drop_stale_initialize_and_keep_one_player_when_selection_is_rapid', () async {
final factory = FakeFactory(holdFirstInitialize: true);
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
final first = session.select(0);
await factory.firstCreated.future;
final second = session.select(1);
factory.created.first.releaseInitialize();
await Future.wait([first, second]);
expect(factory.peakLive, lessThanOrEqualTo(1));
expect(factory.created.first.playCalls, 0);
expect(factory.created.first.disposed, isTrue);
expect(session.currentIndex, 1);
expect(factory.created.last.playCalls, 1);
await session.close();
expect(factory.live, 0);
});
test(
'should_pause_without_forgetting_intent_when_backgrounding_or_overlay_shows',
() async {
final factory = FakeFactory();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
await session.select(0);
final player = factory.created.single;
expect(player.playCalls, 1);
session.setAppResumed(false);
await Future<void>.delayed(Duration.zero);
expect(player.pauseCalls, 1);
session.setAppResumed(true);
await Future<void>.delayed(Duration.zero);
expect(player.playCalls, 2);
session.setOverlayOpen(true);
await Future<void>.delayed(Duration.zero);
expect(player.pauseCalls, 2);
session.setUserWantsPlay(false);
session.setOverlayOpen(false);
await Future<void>.delayed(Duration.zero);
expect(player.playCalls, 2);
await session.close();
},
);
test('should_finish_resume_preparation_before_video_starts_when_resuming', () async {
final factory = FakeFactory();
final prepared = Completer<void>();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
prepare: (_, _) => prepared.future,
);
final selection = session.select(0);
await factory.firstCreated.future;
await Future<void>.delayed(Duration.zero);
expect(factory.created.single.playCalls, 0);
prepared.complete();
await selection;
expect(factory.created.single.playCalls, 1);
await session.close();
});
test('should_resume_playback_when_swiping_after_pause', () async {
final factory = FakeFactory();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
await session.select(0);
session.setUserWantsPlay(false);
await Future<void>.delayed(Duration.zero);
expect(session.userWantsPlay, isFalse);
await session.select(1);
expect(session.userWantsPlay, isTrue);
await Future<void>.delayed(Duration.zero);
final playing = session.active;
expect(playing, isA<FakePlayer>());
expect((playing! as FakePlayer).playCalls, greaterThan(0));
await session.close();
});
test('should_keep_user_pause_when_reselecting_the_same_episode', () async {
final factory = FakeFactory();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
await session.select(0);
session.setUserWantsPlay(false);
await Future<void>.delayed(Duration.zero);
// 页面重新可见等场景会重复选中同一集,这时不能把暂停改成播放。
await session.select(0);
expect(session.userWantsPlay, isFalse);
expect(session.canAutoAdvance, isFalse);
await session.close();
});
test('should_respect_visibility_and_intent_when_advancing_automatically', () async {
final factory = FakeFactory();
final session = PlaybackSession(
episodes: episodes,
factory: factory.create,
canPlay: (episode) => episode.isFree,
);
await session.select(0);
expect(session.canAutoAdvance, isTrue);
session.setRouteVisible(false);
expect(session.canAutoAdvance, isFalse);
session.setRouteVisible(true);
session.setOverlayOpen(true);
expect(session.canAutoAdvance, isFalse);
session.setOverlayOpen(false);
session.setUserWantsPlay(false);
expect(session.canAutoAdvance, isFalse);
await session.close();
});
}
class FakeFactory {
FakeFactory({this.holdFirstInitialize = false});
final bool holdFirstInitialize;
final List<FakePlayer> created = [];
final Completer<void> firstCreated = Completer<void>();
int live = 0;
int peakLive = 0;
PlayerPort create(Episode episode) {
live++;
if (live > peakLive) peakLive = live;
final player = FakePlayer(
holdInitialize: holdFirstInitialize && created.isEmpty,
onDispose: () => live--,
);
created.add(player);
if (!firstCreated.isCompleted) firstCreated.complete();
return player;
}
}
class FakePlayer implements PlayerPort {
FakePlayer({required this.holdInitialize, required this.onDispose});
final bool holdInitialize;
final void Function() onDispose;
final Completer<void> _initializeGate = Completer<void>();
bool initialized = false;
bool disposed = false;
int playCalls = 0;
int pauseCalls = 0;
void releaseInitialize() => _initializeGate.complete();
@override
Future<void> initialize() async {
if (holdInitialize) await _initializeGate.future;
initialized = true;
}
@override
Future<void> play() async => playCalls++;
@override
Future<void> pause() async => pauseCalls++;
@override
Future<void> dispose() async {
if (disposed) return;
disposed = true;
onDispose();
}
}