fix(flutter): 修复详情页跳转灰屏

This commit is contained in:
Zhou Weidong committed 2026-10-20 17:32:53 +08:00
1 parent da78d8723d
commit 353196aaa3
2 files changed
+193 -10

No files matched your search

+181
View File
@@ -0,0 +1,181 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/player_port.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/data/services/android_picture_in_picture.dart';
import 'package:fruitshorts/core/data/services/catalog_controller.dart';
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
import 'package:fruitshorts/core/data/services/playback_session.dart';
import 'package:fruitshorts/core/data/services/video_player_port.dart';
import 'package:fruitshorts/core/platform/haptics.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/drama_detail/drama_detail_view.dart';
import 'package:fruitshorts/pages/player/player_logic.dart';
import 'package:fruitshorts/pages/player/player_view.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
import '../support/fake_catalog.dart';
import '../support/fake_haptics.dart';
import '../support/fake_watch_history.dart';
void main() {
testWidgets(
'should_keep_active_player_and_progress_when_detail_route_is_open',
(tester) async {
final drama = _drama();
final history = FakeWatchHistory();
final playback = FakePlaybackRepository();
final catalog = CatalogController(FakeCatalogRepository(dramas: [drama]));
final favorites = FavoriteController(
InMemoryFavoriteRepository(saved: {drama.id}, cards: [drama]),
);
final logic = PlayerLogic(
history: history,
playback: playback,
pictureInPicture: AndroidPictureInPicture(
channel: const MethodChannel('test/player_open_detail/pip'),
),
);
final factory = _RecordingFactory();
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
await _pumpSession(tester);
final active = logic.session.active;
final index = logic.session.currentIndex;
final player = factory.created.first;
final playsBefore = player.plays;
final tracked = VideoPlayerPort(drama.episodes.first);
tracked.controller.value = const VideoPlayerValue(
duration: Duration(minutes: 5),
position: Duration(seconds: 37),
isInitialized: true,
);
logic
..trackedPlayer = tracked
..trackedEpisode = drama.episodes.first
..trackedDrama = drama;
late BuildContext pageContext;
await tester.pumpWidget(
MultiProvider(
providers: [
Provider<WatchHistoryRepository>.value(value: history),
Provider<PlaybackRepository>.value(value: playback),
ChangeNotifierProvider<CatalogController>.value(value: catalog),
ChangeNotifierProvider<FavoriteController>.value(value: favorites),
Provider<Haptics>.value(value: FakeHaptics()),
],
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) {
pageContext = context;
return const SizedBox.shrink();
},
),
),
),
);
unawaited(logic.openDetail(pageContext));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
await _pumpSession(tester);
expect(find.byType(DramaDetailPage), findsOneWidget);
expect(logic.session.active, same(active));
expect(logic.session.currentIndex, index);
expect(logic.session.userWantsPlay, isTrue);
expect(player.pauses, 1);
expect(player.plays, playsBefore);
expect(tracked.controller.value.position, const Duration(seconds: 37));
expect(history.writes, isEmpty);
await tester.pageBack();
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
await _pumpSession(tester);
await logic.pendingWrite;
expect(logic.session.active, same(active));
expect(logic.session.currentIndex, index);
expect(logic.session.userWantsPlay, isTrue);
expect(player.plays, playsBefore + 1);
expect(tracked.controller.value.position, const Duration(seconds: 37));
expect(history.writes, ['signal-1|37000|false']);
logic
..trackedPlayer = null
..trackedEpisode = null
..trackedDrama = null
..dispose();
await _pumpSession(tester);
await tracked.dispose();
},
);
}
Drama _drama() => Drama(
id: 'signal',
title: 'Signal',
synopsis: 'synopsis',
posterUrl: '/files/signal.png',
episodes: const [
Episode(
id: 'signal-1',
number: 1,
title: 'Episode 1',
videoUrl: 'https://example.test/signal-1.mp4',
isFree: true,
),
],
);
Future<void> _pumpSession(WidgetTester tester) async {
for (var attempt = 0; attempt < 5; attempt++) {
await tester.pump();
await tester.pump(const Duration(milliseconds: 1));
}
}
class _RecordingFactory {
final List<_RecordingPlayer> created = [];
PlayerPort create(Episode episode) {
final player = _RecordingPlayer();
created.add(player);
return player;
}
}
class _RecordingPlayer implements PlayerPort {
int plays = 0;
int pauses = 0;
@override
Future<void> initialize() async {}
@override
Future<void> play() async => plays++;
@override
Future<void> pause() async => pauses++;
@override
Future<void> dispose() async {}
}