import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/config/locale_controller.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/mine/mine_logic.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import '../support/fake_watch_history.dart'; Drama _drama(String id, {List numbers = const [1, 2, 3]}) => Drama( id: id, title: id, synopsis: 'synopsis', posterUrl: '/files/poster.png', episodes: [ for (final number in numbers) Episode( id: '$id-$number', number: number, title: 'Episode $number', videoUrl: 'https://example.test/clip.mp4', isFree: true, ), ], ); void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('should_keep_requested_section_and_read_episode_one_when_constructed', () async { final history = FakeWatchHistory()..saved = const Duration(seconds: 7); final logic = MineLogic( history: history, drama: _drama('signal'), initialSection: 1, ); expect(logic.section, 1); expect(logic.filter, 0); expect(await logic.position, const Duration(seconds: 7)); expect(history.reads, ['signal']); logic.dispose(); }); test('should_resume_from_last_watch_when_list_card_has_no_episodes', () async { final history = FakeWatchHistory() ..saved = const Duration(seconds: 7) ..lastEpisode = 'signal-3'; final logic = MineLogic( history: history, drama: _drama('signal', numbers: const []), initialSection: 1, ); expect(await logic.position, const Duration(seconds: 7)); expect(history.reads, ['signal']); logic.dispose(); }); test('should_notify_when_section_and_filter_are_selected', () { final logic = MineLogic( history: FakeWatchHistory(), drama: _drama('s'), initialSection: 0, ); var notifications = 0; logic.addListener(() => notifications++); logic.selectSection(1); expect(logic.section, 1); expect(notifications, 1); logic.selectFilter(2); expect(logic.filter, 2); expect(notifications, 2); logic.dispose(); }); test('should_adopt_drama_only_when_page_is_active', () { final history = FakeWatchHistory(); final logic = MineLogic( history: history, drama: _drama('signal'), initialSection: 0, ); void sync( Drama next, { required bool active, required bool wasActive, required bool dramaChanged, int sectionRequest = 0, int previousSectionRequest = 0, int initialSection = 0, }) => logic.syncPage( active: active, wasActive: wasActive, nextDrama: next, dramaChanged: dramaChanged, sectionRequest: sectionRequest, previousSectionRequest: previousSectionRequest, initialSection: initialSection, ); sync(_drama('city'), active: false, wasActive: true, dramaChanged: true); expect(logic.drama.id, 'signal'); sync(_drama('city'), active: true, wasActive: false, dramaChanged: false); expect(logic.drama.id, 'city'); history.reads.clear(); sync(_drama('vengeance'), active: true, wasActive: true, dramaChanged: true); expect(logic.drama.id, 'vengeance'); expect(history.reads, ['vengeance']); history.reads.clear(); sync(_drama('other'), active: true, wasActive: true, dramaChanged: false); expect(logic.drama.id, 'vengeance'); expect(history.reads, isEmpty); logic.dispose(); }); test('should_reset_section_when_section_request_arrives_while_page_is_inactive', () { final logic = MineLogic( history: FakeWatchHistory(), drama: _drama('s'), initialSection: 0, ); logic.syncPage( active: false, wasActive: false, nextDrama: logic.drama, dramaChanged: false, sectionRequest: 1, previousSectionRequest: 0, initialSection: 1, ); expect(logic.section, 1); logic.dispose(); }); testWidgets('should_return_choice_and_forward_it_when_act_runs_embedded', ( tester, ) async { final logic = MineLogic( history: FakeWatchHistory(), drama: _drama('s'), initialSection: 0, ); final forwarded = []; final popped = Completer(); late BuildContext pageContext; await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) => TextButton( onPressed: () => Navigator.of(context) .push( MaterialPageRoute( builder: (inner) { pageContext = inner; return const SizedBox.shrink(); }, ), ) .then(popped.complete), child: const Text('open'), ), ), ), ); await tester.tap(find.text('open')); await tester.pumpAndSettle(); logic.act( pageContext, MyPageAction.discover, embedded: true, onAction: forwarded.add, ); expect(forwarded, [MyPageAction.discover]); expect(Navigator.of(pageContext).canPop(), isTrue); logic.act(pageContext, MyPageAction.resume, embedded: false); await tester.pumpAndSettle(); await expectLater(popped.future, completion(MyPageAction.resume)); logic.dispose(); }); testWidgets('should_switch_app_locale_when_language_sheet_is_used', (tester) async { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); final locale = LocaleController(); final logic = MineLogic( history: FakeWatchHistory(), drama: _drama('s'), initialSection: 0, ); late BuildContext context; await tester.pumpWidget( ChangeNotifierProvider.value( value: locale, child: MaterialApp( locale: const Locale('en'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: Builder( builder: (inner) { context = inner; return const Scaffold(body: SizedBox.shrink()); }, ), ), ), ); unawaited(logic.showLanguage(context)); await tester.pumpAndSettle(); final l10n = AppLocalizations.of(context)!; expect(find.text(l10n.english), findsOneWidget); expect(find.text(l10n.simplifiedChinese), findsOneWidget); await tester.tap(find.text(l10n.simplifiedChinese)); await tester.pumpAndSettle(); expect(locale.locale, const Locale('zh')); logic.dispose(); }); test('should_do_nothing_when_notifying_after_dispose', () { final logic = MineLogic( history: FakeWatchHistory(), drama: _drama('s'), initialSection: 0, ); var notifications = 0; logic.addListener(() => notifications++); logic.dispose(); logic.notify(); expect(notifications, 0); }); }