fix(home): 修复频道快速切换卡 loading
This commit is contained in:
1 parent
c8392b2f2a
commit
3c4d65a9b2
5 files changed
+408
-110
No files matched your search
@@ -95,26 +95,109 @@ void main() {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
test('should_ignore_stale_response_when_channels_switch_quickly', () async {
|
||||
final anime = Completer<FeedPage>();
|
||||
final live = Completer<FeedPage>();
|
||||
test(
|
||||
'should_cache_both_channels_when_responses_complete_out_of_order',
|
||||
() async {
|
||||
final anime = Completer<FeedPage>();
|
||||
final live = Completer<FeedPage>();
|
||||
final animeRefresh = Completer<FeedPage>();
|
||||
final repository = _ScriptedFeedRepository()
|
||||
..enqueue(() => anime.future)
|
||||
..enqueue(() => live.future)
|
||||
..enqueue(() => animeRefresh.future);
|
||||
final controller = HomeFeedController(repository);
|
||||
|
||||
final animeLoad = controller.selectChannel(HomeFeedChannel.anime);
|
||||
final liveLoad = controller.selectChannel(HomeFeedChannel.live);
|
||||
live.complete(FeedPage(dramas: [_drama('live')]));
|
||||
await liveLoad;
|
||||
anime.complete(FeedPage(dramas: [_drama('anime')]));
|
||||
await animeLoad;
|
||||
|
||||
expect(controller.channel, HomeFeedChannel.live);
|
||||
expect(controller.dramas.single.id, 'live');
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
expect(controller.dramas.single.id, 'anime');
|
||||
animeRefresh.complete(FeedPage(dramas: [_drama('anime-refresh')]));
|
||||
await pumpEventQueue();
|
||||
expect(controller.dramas.single.id, 'anime-refresh');
|
||||
controller.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test('should_retry_automatically_when_returning_to_failed_channel', () async {
|
||||
final repository = _ScriptedFeedRepository()
|
||||
..enqueue(() => anime.future)
|
||||
..enqueue(() => live.future);
|
||||
..enqueue(() async => throw StateError('offline'))
|
||||
..enqueue(() async => FeedPage(dramas: [_drama('live')]))
|
||||
..enqueue(() async => FeedPage(dramas: [_drama('anime-retry')]));
|
||||
final controller = HomeFeedController(repository);
|
||||
|
||||
final animeLoad = controller.selectChannel(HomeFeedChannel.anime);
|
||||
final liveLoad = controller.selectChannel(HomeFeedChannel.live);
|
||||
live.complete(FeedPage(dramas: [_drama('live')]));
|
||||
await liveLoad;
|
||||
anime.complete(FeedPage(dramas: [_drama('anime')]));
|
||||
await animeLoad;
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
expect(controller.lastError, contains('offline'));
|
||||
expect(controller.loadingFirstPage, isFalse);
|
||||
|
||||
expect(controller.channel, HomeFeedChannel.live);
|
||||
expect(controller.dramas.single.id, 'live');
|
||||
await controller.selectChannel(HomeFeedChannel.live);
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
|
||||
expect(controller.dramas.single.id, 'anime-retry');
|
||||
expect(repository.calls, hasLength(3));
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
test('should_not_report_first_page_loading_without_inflight_request', () {
|
||||
final controller = HomeFeedController(_ScriptedFeedRepository());
|
||||
|
||||
expect(controller.loaded, isFalse);
|
||||
expect(controller.loadingFirstPage, isFalse);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
test('should_finish_loading_with_error_when_request_times_out', () async {
|
||||
final pending = Completer<FeedPage>();
|
||||
final repository = _ScriptedFeedRepository()..enqueue(() => pending.future);
|
||||
final controller = HomeFeedController(
|
||||
repository,
|
||||
requestTimeout: const Duration(milliseconds: 10),
|
||||
);
|
||||
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
|
||||
expect(controller.loadingFirstPage, isFalse);
|
||||
expect(controller.loaded, isTrue);
|
||||
expect(controller.lastError, isNotNull);
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
test(
|
||||
'should_ignore_old_failure_when_same_channel_new_request_succeeds',
|
||||
() async {
|
||||
final oldRefresh = Completer<FeedPage>();
|
||||
final repository = _ScriptedFeedRepository()
|
||||
..enqueue(
|
||||
() async => FeedPage(
|
||||
dramas: [_drama('a')],
|
||||
nextCursor: 'next',
|
||||
hasMore: true,
|
||||
),
|
||||
)
|
||||
..enqueue(() async => FeedPage(dramas: [_drama('live')]))
|
||||
..enqueue(() => oldRefresh.future)
|
||||
..enqueue(() async => FeedPage(dramas: [_drama('b')], hasMore: false));
|
||||
final controller = HomeFeedController(repository);
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
await controller.selectChannel(HomeFeedChannel.live);
|
||||
await controller.selectChannel(HomeFeedChannel.anime);
|
||||
|
||||
await controller.loadMore();
|
||||
oldRefresh.completeError(StateError('old failure'));
|
||||
await pumpEventQueue();
|
||||
|
||||
expect(controller.dramas.map((drama) => drama.id), ['a', 'b']);
|
||||
expect(controller.lastError, isNull);
|
||||
controller.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test('should_keep_old_data_when_refresh_fails', () async {
|
||||
final repository = _ScriptedFeedRepository()
|
||||
..enqueue(() async => FeedPage(dramas: [_drama('old')]))
|
||||
|
||||
@@ -431,7 +431,7 @@ void main() {
|
||||
final activeBefore = logic.session.active;
|
||||
|
||||
logic.updateFeed([...feed, _drama('third')], keepDramaId: 'signal');
|
||||
await pumpEventQueue();
|
||||
await logic.feedUpdateSettled;
|
||||
|
||||
expect(logic.currentDramaId, 'signal');
|
||||
expect(logic.visibleIndices, [0, 1, 2]);
|
||||
@@ -463,7 +463,7 @@ void main() {
|
||||
[_drama('city'), _drama('signal'), _drama('third')],
|
||||
keepDramaId: 'city',
|
||||
);
|
||||
await pumpEventQueue();
|
||||
await logic.feedUpdateSettled;
|
||||
|
||||
expect(logic.currentDramaId, 'city');
|
||||
expect(logic.session.currentIndex, 0);
|
||||
@@ -488,7 +488,11 @@ void main() {
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
|
||||
logic.updateFeed([_drama('anime-a'), _drama('anime-b')]);
|
||||
logic.updateFeed(
|
||||
[_drama('anime-a'), _drama('anime-b')],
|
||||
resetToFirst: true,
|
||||
);
|
||||
await logic.feedUpdateSettled;
|
||||
await _drain(logic);
|
||||
|
||||
expect(logic.queue.entryAt(0).drama.id, 'anime-a');
|
||||
@@ -517,6 +521,7 @@ void main() {
|
||||
await _drain(logic);
|
||||
|
||||
logic.updateFeed(const []);
|
||||
await logic.feedUpdateSettled;
|
||||
|
||||
expect(logic.queue.length, 0);
|
||||
expect(logic.visibleIndices, isEmpty);
|
||||
@@ -524,6 +529,95 @@ void main() {
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_apply_only_latest_feed_when_update_is_called_five_times',
|
||||
() async {
|
||||
final factory = _RecordingFactory();
|
||||
final logic = _logic(FakeWatchHistory());
|
||||
final initial = [_drama('initial')];
|
||||
logic.attach(
|
||||
PlayerPage(
|
||||
drama: initial.first,
|
||||
initialIndex: 0,
|
||||
homeMode: true,
|
||||
feedDramas: initial,
|
||||
),
|
||||
isMounted: () => true,
|
||||
);
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
|
||||
for (var index = 1; index <= 5; index++) {
|
||||
logic.updateFeed([_drama('feed-$index')], resetToFirst: true);
|
||||
}
|
||||
await logic.feedUpdateSettled;
|
||||
await _drain(logic);
|
||||
|
||||
expect(logic.feedRevision, 5);
|
||||
expect(logic.queue.entryAt(0).drama.id, 'feed-5');
|
||||
expect(logic.sessionEpisodes.single.id, 'feed-5-1');
|
||||
expect(logic.currentDramaId, 'feed-5');
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_select_first_item_when_feed_recovers_from_empty', () async {
|
||||
final factory = _RecordingFactory();
|
||||
final feed = [_drama('signal')];
|
||||
final logic = _logic(FakeWatchHistory());
|
||||
logic.attach(
|
||||
PlayerPage(
|
||||
drama: feed.first,
|
||||
initialIndex: 0,
|
||||
homeMode: true,
|
||||
feedDramas: feed,
|
||||
),
|
||||
isMounted: () => true,
|
||||
);
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
logic.updateFeed(const []);
|
||||
await logic.feedUpdateSettled;
|
||||
|
||||
logic.updateFeed([_drama('restored')], resetToFirst: true);
|
||||
await logic.feedUpdateSettled;
|
||||
await _drain(logic);
|
||||
|
||||
expect(logic.queue.entryAt(0).drama.id, 'restored');
|
||||
expect(logic.session.currentIndex, 0);
|
||||
expect(logic.currentDramaId, 'restored');
|
||||
expect(factory.live, greaterThan(0));
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_reset_to_first_when_channel_changes_with_same_ids',
|
||||
() async {
|
||||
final factory = _RecordingFactory();
|
||||
final feed = [_drama('same-a'), _drama('same-b')];
|
||||
final logic = _logic(FakeWatchHistory());
|
||||
logic.attach(
|
||||
PlayerPage(
|
||||
drama: feed.first,
|
||||
initialIndex: 1,
|
||||
homeMode: true,
|
||||
feedDramas: feed,
|
||||
),
|
||||
isMounted: () => true,
|
||||
);
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
expect(logic.session.currentIndex, 1);
|
||||
|
||||
logic.updateFeed(feed, resetToFirst: true);
|
||||
await logic.feedUpdateSettled;
|
||||
await _drain(logic);
|
||||
|
||||
expect(logic.session.currentIndex, 0);
|
||||
expect(logic.currentDramaId, 'same-a');
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
});
|
||||
|
||||
group('lifecycle', () {
|
||||
|
||||
Reference in new issue
Block a user