import 'dart:io'; import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:focus_seed/src/app/focus_seed_app.dart'; import 'package:focus_seed/src/data/focus_seed_store.dart'; import 'package:focus_seed/src/data/local/focus_seed_database.dart'; import 'package:focus_seed/src/models/app_state.dart'; import 'package:focus_seed/src/models/focus_session.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); test('locale policy defaults to English for release and Chinese for dev', () { expect(focusSeedDefaultLocale(isRelease: true), const Locale('en')); expect(focusSeedDefaultLocale(), const Locale('zh')); }); test('welcome art uses managed onboarding assets', () { final welcomeSource = File( 'lib/src/features/onboarding/welcome_screen.dart', ).readAsStringSync(); final landingSource = File( 'lib/src/features/onboarding/landing_setup_screen.dart', ).readAsStringSync(); final recordsSource = File('lib/src/features/records/records_screen.dart').readAsStringSync(); expect(welcomeSource, contains('Image.asset')); expect(welcomeSource, contains('AppAssets.welcomePath')); expect(welcomeSource, contains('AppAssets.focusBlock')); expect(welcomeSource, contains('AppAssets.reflectionNote')); expect(landingSource, contains('AppAssets.landingFocusStart')); expect(recordsSource, contains('AppAssets.recordsEmpty')); expect(welcomeSource, isNot(contains('KineticIllustration'))); expect(welcomeSource, isNot(contains('SingleChildScrollView'))); expect(welcomeSource, isNot(contains('Parallax'))); expect(landingSource, isNot(contains('KineticIllustration'))); expect(recordsSource, isNot(contains('KineticIllustration'))); expect(welcomeSource, isNot(contains('SeedIllustration'))); expect(welcomeSource, isNot(contains('MiniTerrainMark'))); }); testWidgets('Today keeps the core loop free of saved plan noise', ( tester, ) async { await pumpFocusSeedApp(tester, locale: const Locale('en')); expect(find.text('Focus Seed'), findsOneWidget); expect(find.text('Start 10 min'), findsWidgets); expect(find.text('Name today\'s focus'), findsOneWidget); expect(find.text('Pick a focus plan'), findsNothing); expect(find.text('Deep work sprint'), findsNothing); }); testWidgets('first launch shows welcome before Today', (tester) async { final database = FocusSeedDatabase(NativeDatabase.memory()); addTearDown(database.close); await tester.pumpWidget( FocusSeedApp( locale: const Locale('en'), focusSeedStore: FocusSeedStore(database: database), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 300)); expect(find.text('Plant just one thing each day.'), findsOneWidget); expect(find.text('Continue'), findsOneWidget); expect(find.byType(Image), findsWidgets); await tester.tap(find.text('Continue')); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); expect(find.text('Protect 10 minutes first.'), findsOneWidget); expect(find.byType(Image), findsWidgets); await tester.tap(find.text('Continue')); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); expect(find.text('Leave one progress note.'), findsOneWidget); expect(find.byType(Image), findsWidgets); await tester.tap(find.text('Set today\'s focus')); await tester.pump(); await tester.pump(const Duration(milliseconds: 700)); expect(find.text('Start with 10 minutes.'), findsOneWidget); expect(find.text('Write down what you will do'), findsOneWidget); expect(find.text('What do you want to do first?'), findsNothing); await tester.tap(find.text('Write down what you will do')); await tester.pumpAndSettle(); expect(find.text('What do you want to do first?'), findsOneWidget); await tester.ensureVisible(find.text('Start 10 minutes')); await tester.pumpAndSettle(); final enterTodayButton = find.ancestor( of: find.text('Start 10 minutes'), matching: find.byWidgetPredicate((widget) => widget is ButtonStyleButton), ); expect(enterTodayButton, findsOneWidget); expect( (tester.widget(enterTodayButton) as ButtonStyleButton).onPressed, isNotNull, ); await tester.tap(enterTodayButton); await tester.pumpAndSettle(); expect(find.text('Deep work in progress'), findsOneWidget); expect(find.text('Start this session first'), findsOneWidget); }); testWidgets('landing setup can save typed action before starting', ( tester, ) async { SharedPreferences.setMockInitialValues({ 'focus_seed_onboarding_complete': true, 'focus_seed_landing_setup_complete': false, }); final database = FocusSeedDatabase(NativeDatabase.memory()); addTearDown(database.close); await tester.pumpWidget( FocusSeedApp( locale: const Locale('en'), focusSeedStore: FocusSeedStore(database: database), ), ); await tester.pumpAndSettle(); await tester.tap(find.text('Write down what you will do')); await tester.pumpAndSettle(); await tester.enterText( find.byType(TextField), 'Finish the launch checklist', ); await tester.ensureVisible(find.text('Start 10 minutes')); await tester.pumpAndSettle(); await tester.tap(find.text('Start 10 minutes')); await tester.pumpAndSettle(); expect( find.text('Today\'s focus: Finish the launch checklist'), findsOneWidget, ); expect(find.text('10 min'), findsWidgets); }); testWidgets('finished onboarding restores landing until it is completed', ( tester, ) async { SharedPreferences.setMockInitialValues({ 'focus_seed_onboarding_complete': true, 'focus_seed_landing_setup_complete': false, }); final database = FocusSeedDatabase(NativeDatabase.memory()); addTearDown(database.close); await tester.pumpWidget( FocusSeedApp( locale: const Locale('en'), focusSeedStore: FocusSeedStore(database: database), ), ); await tester.pumpAndSettle(); expect(find.text('Start with 10 minutes.'), findsOneWidget); expect(find.text('Focus Seed'), findsNothing); await tester.ensureVisible(find.text('Enter Today')); await tester.pumpAndSettle(); await tester.tap(find.text('Enter Today')); await tester.pumpAndSettle(); expect(find.text('Focus Seed'), findsOneWidget); expect(find.text('Start with 10 minutes.'), findsNothing); await tester.pumpWidget( FocusSeedApp( locale: const Locale('en'), focusSeedStore: FocusSeedStore(database: database), ), ); await tester.pumpAndSettle(); expect(find.text('Focus Seed'), findsOneWidget); expect(find.text('Start with 10 minutes.'), findsNothing); }); testWidgets('bottom navigation exposes Focus, Records, and Me', ( tester, ) async { await pumpFocusSeedApp(tester, locale: const Locale('en')); await tester.tap(_navDestination('Focus')); await tester.pumpAndSettle(); expect(find.text('Ready when you are'), findsOneWidget); await tester.tap(_navDestination('Records')); await tester.pumpAndSettle(); expect(find.text('Records'), findsWidgets); expect(find.text('No progress records yet'), findsOneWidget); await tester.tap(_navDestination('Me')); await tester.pumpAndSettle(); expect(find.text('Me'), findsWidgets); expect(find.text('Preferences'), findsWidgets); await tester.tap(_navDestination('Today')); await tester.pumpAndSettle(); expect(find.text('Name today\'s focus'), findsOneWidget); }); testWidgets('V1 action loop records a session and capture', (tester) async { await pumpFocusSeedApp(tester, locale: const Locale('en')); final startButton = find.ancestor( of: find.text('Start 10 min').last, matching: find.byWidgetPredicate((widget) => widget is ButtonStyleButton), ); await tester.ensureVisible(startButton); await tester.pumpAndSettle(); await tester.tap(startButton); await tester.pumpAndSettle(); expect(find.text('Deep work in progress'), findsOneWidget); await tester.tap(find.text('Pause')); await tester.pumpAndSettle(); expect(find.text('Paused - your place is kept'), findsOneWidget); await tester.tap(find.text('Resume')); await tester.pumpAndSettle(); expect(find.text('Deep work in progress'), findsOneWidget); await tester.tap(find.text('Complete session')); await tester.pumpAndSettle(); expect(find.text('Session complete'), findsOneWidget); expect(find.text('Write progress note'), findsOneWidget); await tester.tap(find.text('Write progress note')); await tester.pumpAndSettle(); expect(find.text('Leave one progress note'), findsOneWidget); await tester.enterText(find.byType(TextField).last, 'Finished one pass.'); await tester.tap(find.text('Save note')); await tester.pumpAndSettle(); await tester.tap(_navDestination('Records')); await tester.pumpAndSettle(); expect(find.text('Records'), findsWidgets); expect(find.text('Finished one pass.'), findsOneWidget); expect(find.text('No progress records yet'), findsNothing); }); testWidgets('Records can backfill reflection for a past daily record', ( tester, ) async { final database = FocusSeedDatabase(NativeDatabase.memory()); addTearDown(database.close); final store = FocusSeedStore(database: database); final now = DateTime.now(); final yesterday = now.subtract(const Duration(days: 1)); await store.save( AppState( anchor: '', anchorDateKey: '', habits: const [], sessions: [ FocusSession( id: 'session-yesterday', minutes: 10, actualSeconds: 600, completedAt: yesterday, intention: 'Draft launch notes', ), ], reflections: const [], ), ); await tester.pumpWidget( FocusSeedApp( locale: const Locale('en'), focusSeedStore: store, showWelcomeOnFirstRun: false, ), ); await tester.pumpAndSettle(); await tester.tap(_navDestination('Records')); await tester.pumpAndSettle(); expect(find.text('Draft launch notes'), findsWidgets); expect(find.text('No progress note yet'), findsWidgets); expect(find.text('Write one sentence to close today'), findsOneWidget); expect(find.text('Continue this'), findsNothing); final reflectionButton = find.ancestor( of: find.text('Write one sentence to close today'), matching: find.byWidgetPredicate((widget) => widget is ButtonStyleButton), ); await tester.ensureVisible(reflectionButton); await tester.pumpAndSettle(); await tester.tap(reflectionButton); await tester.pumpAndSettle(); expect(find.text('Leave one progress note'), findsOneWidget); expect(find.text('TODAY\'S FOCUS'), findsWidgets); expect(find.text('Draft launch notes'), findsWidgets); await tester.enterText(find.byType(TextField).last, 'Next: polish copy.'); final saveNoteButton = find.ancestor( of: find.text('Save note'), matching: find.byWidgetPredicate((widget) => widget is ButtonStyleButton), ); await tester.ensureVisible(saveNoteButton); await tester.pumpAndSettle(); await tester.tap(saveNoteButton); await tester.pumpAndSettle(); expect(find.text('Next: polish copy.'), findsOneWidget); expect(find.text('Write one sentence to close today'), findsNothing); expect(find.text('Continue this'), findsNothing); }); testWidgets('settings manages local data without JSON tools', (tester) async { await pumpFocusSeedApp(tester, locale: const Locale('en')); await tester.tap(_navDestination('Me')); await tester.pumpAndSettle(); var meScroll = find.byType(Scrollable).first; expect(find.text('Personal rhythm'), findsNothing); expect(find.text('Saved plans'), findsNothing); await tester.scrollUntilVisible( find.text('Theme, language, and default focus'), 180, scrollable: meScroll, ); final preferencesRow = find.ancestor( of: find.text('Theme, language, and default focus'), matching: find.byType(InkWell), ); expect(preferencesRow, findsOneWidget); await tester.tap(preferencesRow); await tester.pumpAndSettle(); expect(find.text('Default focus'), findsOneWidget); expect(find.text('10 min'), findsWidgets); expect(find.text('1'), findsOneWidget); expect(find.text('5'), findsOneWidget); expect(find.text('45'), findsOneWidget); await tester.tap(find.byIcon(Icons.add_rounded).last); await tester.pumpAndSettle(); expect(find.text('Custom length'), findsOneWidget); await tester.enterText(find.byType(TextField), '7'); await tester.tap(find.text('Save')); await tester.pumpAndSettle(); expect(find.text('7 min'), findsOneWidget); await tester.pageBack(); await tester.pumpAndSettle(); meScroll = find.byType(Scrollable).first; await tester.scrollUntilVisible( find.text('Local records'), 180, scrollable: meScroll, ); expect(find.text('Local records'), findsOneWidget); expect(find.text('Clear today'), findsOneWidget); await tester.scrollUntilVisible( find.text('Delete all data'), 180, scrollable: meScroll, ); expect(find.text('Delete all data'), findsOneWidget); expect(find.text('Export JSON'), findsNothing); expect(find.text('Import JSON'), findsNothing); }); testWidgets('Records shows empty focus and reflection states', ( tester, ) async { await pumpFocusSeedApp(tester, locale: const Locale('en')); await tester.tap(_navDestination('Records')); await tester.pumpAndSettle(); expect(find.text('Records'), findsWidgets); expect(find.text('No progress records yet'), findsOneWidget); expect(find.text('Back to Today'), findsOneWidget); }); testWidgets('Today remains usable at narrow width and 200 percent text', ( tester, ) async { tester.view.physicalSize = const Size(320, 700); tester.view.devicePixelRatio = 1; tester.platformDispatcher.textScaleFactorTestValue = 2; addTearDown(() { tester.view.resetPhysicalSize(); tester.view.resetDevicePixelRatio(); tester.platformDispatcher.clearTextScaleFactorTestValue(); }); await pumpFocusSeedApp(tester, locale: const Locale('en')); expect(find.text('Focus Seed'), findsOneWidget); expect(find.byType(Scrollable), findsWidgets); expect(tester.takeException(), isNull); }); testWidgets('development default uses concise Chinese copy', (tester) async { await pumpFocusSeedApp(tester); expect(find.text('Focus Seed'), findsOneWidget); expect( find.text('\u5199\u4e0b\u4eca\u5929\u7684\u91cd\u70b9'), findsOneWidget, ); expect(find.text('\u5f00\u59cb 10 \u5206\u949f'), findsWidgets); expect( find.text('\u9009\u62e9\u4e00\u4e2a\u4e13\u6ce8\u8ba1\u5212'), findsNothing, ); expect(find.text('\u6df1\u5ea6\u5de5\u4f5c'), findsNothing); }); testWidgets('development language switch changes the active locale', ( tester, ) async { await pumpFocusSeedApp(tester); await tester.tap(_navDestination('\u6211\u7684')); await tester.pumpAndSettle(); final settingsScroll = find.byType(Scrollable).first; await tester.scrollUntilVisible( find.text('\u4e3b\u9898\u3001\u8bed\u8a00\u548c\u9ed8\u8ba4\u4e13\u6ce8'), 180, scrollable: settingsScroll, ); final preferencesRow = find.ancestor( of: find.text( '\u4e3b\u9898\u3001\u8bed\u8a00\u548c\u9ed8\u8ba4\u4e13\u6ce8', ), matching: find.byType(InkWell), ); await tester.tap(preferencesRow); await tester.pumpAndSettle(); expect(find.text('\u8bed\u8a00'), findsOneWidget); final languageRow = find.ancestor( of: find.text('\u8bed\u8a00'), matching: find.byType(InkWell), ); expect(languageRow, findsOneWidget); await tester.tap(languageRow); await tester.pumpAndSettle(); await tester.tap(find.text('English')); await tester.pumpAndSettle(); expect(find.text('Language'), findsOneWidget); expect(find.text('English'), findsOneWidget); }); } Future pumpFocusSeedApp(WidgetTester tester, {Locale? locale}) async { final database = FocusSeedDatabase(NativeDatabase.memory()); addTearDown(database.close); await tester.pumpWidget( FocusSeedApp( locale: locale, focusSeedStore: FocusSeedStore(database: database), showWelcomeOnFirstRun: false, ), ); await tester.pumpAndSettle(); } Finder _navDestination(String label) { final key = switch (label) { 'Today' || '\u4eca\u65e5' => 'today', 'Focus' || '\u4e13\u6ce8' => 'focus', 'Records' || '\u8bb0\u5f55' => 'records', 'Me' || '\u6211\u7684' => 'me', _ => label.toLowerCase(), }; return find.byKey(ValueKey('focus-seed-nav-$key')); }