mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-10 21:07:02 +01:00
Every split is by test-group/concern, with the shared setup moved to a
sibling helper the splits import. All 313 tests are preserved -- the count
is unchanged and `test('…')` names are byte-identical to the originals
(verified by diffing the sorted name lists).
note_repository_test 677 -> 4 files + note_repository_helpers
capture_screen_test 660 -> 4 files + capture_screen_harness
note_editor_test 502 -> 3 files + note_editor_harness
port_guard_test 405 -> 3 files + port_guard_fakes
wrapper_server_test 402 -> 2 files + wrapper_server_harness
settings_screen_test 391 -> 2 files + settings_screen_harness
github_mirror_screen_test 365 -> 2 files + github_mirror_harness
notes_list_screen_test 347 -> 2 files + notes_list_harness
analytics_service_test 300 -> 2 files + analytics_helpers
Helpers are deliberately NOT named `*_test.dart`; the runner would collect
them and fail on the missing `main()`.
Three things did not survive a mechanical split and were handled explicitly:
* wrapper_server's `enabledOrigin` closes over the `late root` that setUp
assigns, so a top-level function cannot see it. It is now a
WrapperHarness class with an install() that registers the setUp/tearDown
pair -- the only structural change in this commit.
* note_repository's splits each repeat `setUpAll(sqfliteFfiInit)`, and
analytics' each repeat `@TestOn('vm')`. Omitting either fails quietly
(uninitialised sqflite / wrong platform) rather than loudly.
* capture_screen's `recordingMock` was declared mid-file between two
tests, so it moved to the harness rather than being dropped.
Verified: 313 tests pass (same as before the refactor), 100% coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
162 lines
5.4 KiB
Dart
162 lines
5.4 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:todo/analytics/analytics_service.dart';
|
|
import 'package:todo/data/app_settings.dart';
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/data/note_template.dart';
|
|
import 'package:todo/sync/local_backup.dart';
|
|
import 'package:todo/sync/notes_markdown.dart';
|
|
import 'package:todo/ui/capture_screen.dart';
|
|
import 'package:todo/ui/github_mirror_screen.dart';
|
|
import 'package:todo/ui/settings_screen.dart';
|
|
|
|
import 'fake_note_repository.dart';
|
|
import 'fake_secure_storage.dart';
|
|
|
|
import 'capture_screen_harness.dart';
|
|
|
|
void main() {
|
|
testWidgets('defaults to Raw, with Guided available via the entry wizard', (
|
|
tester,
|
|
) async {
|
|
await pumpCapture(tester);
|
|
|
|
// Defaults to Raw: a single text field, no note persisted yet.
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
expect(find.text('Guided'), findsOneWidget);
|
|
expect(find.text('0'), findsOneWidget);
|
|
|
|
// Tapping Guided on the empty draft opens the priority+template wizard
|
|
// rather than jumping straight into the stepper.
|
|
await tester.tap(find.text('Guided'));
|
|
await tester.pump();
|
|
expect(find.text('Step 1 of 2'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Next'));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Start'));
|
|
await tester.pump();
|
|
|
|
// Now on step 1 of the full-screen step page.
|
|
expect(find.byType(LinearProgressIndicator), findsOneWidget);
|
|
expect(
|
|
find.text('1 / ${NoteTemplate.llmDesignSpec.sections.length}'),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.textContaining('imperative'), findsOneWidget); // title helper
|
|
});
|
|
|
|
testWidgets('saving the untouched template creates no note', (tester) async {
|
|
final repo = await pumpCapture(tester);
|
|
|
|
await tester.tap(find.byTooltip('New note'));
|
|
await tester.pump();
|
|
|
|
expect(await repo.listNotes(), isEmpty);
|
|
});
|
|
|
|
testWidgets('typing into the template persists a note with defaults', (
|
|
tester,
|
|
) async {
|
|
final repo = await pumpCapture(tester);
|
|
|
|
// The first field in the guided stepper is the title section.
|
|
await tester.enterText(find.byType(TextField).first, 'My idea');
|
|
await tester.pump();
|
|
|
|
final notes = await repo.listNotes();
|
|
expect(notes, hasLength(1));
|
|
expect(notes.single.text, contains('My idea'));
|
|
expect(notes.single.priority, Priority.medium);
|
|
expect(notes.single.status, Status.todo);
|
|
expect(find.text('1'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('the new-note action resets the template', (tester) async {
|
|
final repo = await pumpCapture(tester);
|
|
|
|
await tester.enterText(find.byType(TextField).first, 'A real idea');
|
|
await tester.pump();
|
|
await tester.tap(find.byTooltip('New note'));
|
|
await tester.pump();
|
|
|
|
expect(await repo.listNotes(), hasLength(1));
|
|
// The editor reset to a fresh, empty Raw draft.
|
|
expect(find.byType(TextField), findsOneWidget);
|
|
final raw = tester.widget<TextField>(find.byType(TextField));
|
|
expect(raw.controller!.text, isEmpty);
|
|
});
|
|
|
|
testWidgets('tapping Sync while unconfigured prompts to connect a backend', (
|
|
tester,
|
|
) async {
|
|
await pumpCapture(tester); // empty prefs → no token → not configured
|
|
|
|
await tester.tap(find.byTooltip('Sync'));
|
|
await tester.pump(); // settings load + snackbar
|
|
await tester.pump();
|
|
|
|
// The behaviour that matters is the routing, not the wording: an
|
|
// unconfigured device is sent to settings to connect a backend. Asserting
|
|
// on the snackbar text tied this to one phrasing, and the snackbar is
|
|
// gone by the time settings has pushed anyway.
|
|
expect(find.textContaining('Connect Firebase'), findsWidgets);
|
|
});
|
|
|
|
testWidgets('the notes-list button navigates to the list screen', (
|
|
tester,
|
|
) async {
|
|
await pumpCapture(tester);
|
|
|
|
await tester.tap(find.byTooltip('Notes'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300)); // route transition
|
|
|
|
expect(find.text('Notes'), findsOneWidget); // list screen app bar title
|
|
});
|
|
|
|
testWidgets('changing the priority dropdown updates the saved note', (
|
|
tester,
|
|
) async {
|
|
final repo = await pumpCapture(tester);
|
|
|
|
await tester.enterText(find.byType(TextField).first, 'Prioritised idea');
|
|
await tester.pump();
|
|
|
|
await tester.tap(
|
|
find.byWidgetPredicate((w) => w is DropdownButton<Priority>),
|
|
);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400)); // menu open
|
|
await tester.tap(find.text('High').last);
|
|
await tester.pump();
|
|
|
|
expect((await repo.listNotes()).single.priority, Priority.high);
|
|
});
|
|
|
|
testWidgets('changing the status dropdown updates the saved note', (
|
|
tester,
|
|
) async {
|
|
final repo = await pumpCapture(tester);
|
|
|
|
await tester.enterText(find.byType(TextField).first, 'Status idea');
|
|
await tester.pump();
|
|
|
|
await tester.tap(
|
|
find.byWidgetPredicate((w) => w is DropdownButton<Status>),
|
|
);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400)); // menu open
|
|
await tester.tap(find.text('In progress').last);
|
|
await tester.pump();
|
|
|
|
expect((await repo.listNotes()).single.status, Status.inProgress);
|
|
});
|
|
}
|