首次远程推送

This commit is contained in:
2026-09-27 23:40:22 +08:00
parent 2e0bf3c784
commit 8afa47952a
70 changed files with 8603 additions and 4502 deletions
+388 -62
View File
@@ -1,9 +1,13 @@
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() {
@@ -16,45 +20,352 @@ void main() {
expect(focusSeedDefaultLocale(), const Locale('zh'));
});
testWidgets('Today guides the user from intention to focus', (tester) async {
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("TODAY'S FOCUS"), findsOneWidget);
expect(find.text("Set today's focus"), 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);
});
await tester.tap(find.text("Name today's focus"));
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('Finish the launch checklist'), findsOneWidget);
expect(find.text('Start 25 min'), findsOneWidget);
});
testWidgets('bottom navigation exposes Journey and Me', (tester) async {
await pumpFocusSeedApp(tester, locale: const Locale('en'));
await tester.tap(find.text('Journey'));
expect(find.text('7 min'), findsOneWidget);
await tester.pageBack();
await tester.pumpAndSettle();
expect(find.text('Progress'), findsOneWidget);
await tester.tap(find.text('Me'));
await tester.pumpAndSettle();
expect(find.text('Your garden'), findsOneWidget);
});
testWidgets('Me manages local data without JSON tools', (tester) async {
await pumpFocusSeedApp(tester, locale: const Locale('en'));
await tester.tap(find.text('Me'));
await tester.pumpAndSettle();
final meScroll = find.byType(Scrollable).first;
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(
@@ -67,35 +378,17 @@ void main() {
expect(find.text('Import JSON'), findsNothing);
});
testWidgets('Journey opens history detail with empty states', (tester) async {
testWidgets('Records shows empty focus and reflection states', (
tester,
) async {
await pumpFocusSeedApp(tester, locale: const Locale('en'));
await tester.tap(find.text('Journey'));
await tester.tap(_navDestination('Records'));
await tester.pumpAndSettle();
final journeyScroll = find.byType(Scrollable).first;
await tester.scrollUntilVisible(
find.text('7-day rhythm'),
180,
scrollable: journeyScroll,
);
expect(find.text('7-day rhythm'), findsOneWidget);
await tester.scrollUntilVisible(
find.text('Record library'),
180,
scrollable: journeyScroll,
);
expect(find.text('Record library'), findsOneWidget);
await tester.tap(find.text('Record library'));
await tester.pumpAndSettle();
expect(find.text('History'), findsOneWidget);
expect(find.text('No focus sessions yet'), findsOneWidget);
await tester.tap(find.text('Notes'));
await tester.pumpAndSettle();
expect(find.text('No notes yet'), findsOneWidget);
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', (
@@ -121,16 +414,16 @@ void main() {
await pumpFocusSeedApp(tester);
expect(find.text('Focus Seed'), findsOneWidget);
expect(find.text('\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
expect(find.text('\u8bbe\u7f6e\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
await tester.scrollUntilVisible(
find.text('\u8ba1\u5212\u4e00\u4e2a\u660e\u786e\u4efb\u52a1'),
180,
);
expect(
find.text('\u8ba1\u5212\u4e00\u4e2a\u660e\u786e\u4efb\u52a1'),
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', (
@@ -138,15 +431,36 @@ void main() {
) async {
await pumpFocusSeedApp(tester);
await tester.tap(find.text('\u6211\u7684'));
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);
await tester.tap(find.byIcon(Icons.language_rounded));
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('Development only'), findsOneWidget);
expect(find.text('English'), findsOneWidget);
});
}
@@ -157,7 +471,19 @@ Future<void> pumpFocusSeedApp(WidgetTester tester, {Locale? locale}) async {
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'));
}