diff --git a/docs/progress.md b/docs/progress.md index 39bd446..99d8d20 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -7,6 +7,25 @@ Updated: 2026-02-22 - After every sensible code/documentation change set, create a git commit as the last step so the next agent session can pick up from clean checkpoints. +## Latest Milestone (2026-02-22): Quick-Capture Dialog Regression Test (People) + +Added explicit widget-test coverage so controller lifecycle issues in quick +capture dialogs are caught automatically in CI/local runs. + +- `test/features/people/people_quick_capture_dialog_test.dart` (new) + - mounts `PeopleView` in mobile layout + - opens profile-context `Capture` quick action dialog + - exercises `Cancel` path and pumps through dismissal frames + - reopens dialog, saves a capture, and pumps through dismissal frames + - asserts: + - no framework exceptions surfaced (`tester.takeException() == null`) + - local moments count increases after save + - specifically protects against the disposed `TextEditingController` race + +- Validation + - `flutter analyze` -> pass + - `flutter test` -> pass + ## Latest Milestone (2026-02-22): Quick-Capture Dialog Controller Lifecycle Fix Fixed a runtime crash when creating captures/notes from profile-context quick diff --git a/test/features/people/people_quick_capture_dialog_test.dart b/test/features/people/people_quick_capture_dialog_test.dart new file mode 100644 index 0000000..31086e6 --- /dev/null +++ b/test/features/people/people_quick_capture_dialog_test.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/core/config/app_theme.dart'; +import 'package:relationship_saver/features/local/local_models.dart'; +import 'package:relationship_saver/features/local/local_repository.dart'; +import 'package:relationship_saver/features/local/storage/local_data_store_in_memory.dart'; +import 'package:relationship_saver/features/local/storage/local_data_store_provider.dart'; +import 'package:relationship_saver/features/people/people_view.dart'; +import 'package:relationship_saver/features/sync/storage/sync_state_store_in_memory.dart'; +import 'package:relationship_saver/features/sync/storage/sync_state_store_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + setUp(() { + SharedPreferences.setMockInitialValues({}); + }); + + testWidgets( + 'people quick capture dialog can cancel and save without controller lifecycle errors', + (WidgetTester tester) async { + await tester.binding.setSurfaceSize(const Size(390, 844)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + + final ProviderContainer container = ProviderContainer( + overrides: [ + localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()), + syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()), + ], + ); + addTearDown(container.dispose); + + final LocalDataState initial = await container.read( + localRepositoryProvider.future, + ); + final int beforeMomentsCount = initial.moments.length; + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: MaterialApp( + theme: AppTheme.light(), + home: const Scaffold(body: PeopleView()), + ), + ), + ); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); + + expect(find.text('People'), findsWidgets); + + await tester.tap(find.widgetWithText(FilledButton, 'Capture').first); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + expect(find.text('Add Capture'), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Cancel'), findsOneWidget); + + await tester.tap(find.widgetWithText(TextButton, 'Cancel')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 250)); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + expect(tester.takeException(), isNull); + + await tester.tap(find.widgetWithText(FilledButton, 'Capture').first); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + expect(find.text('Add Capture'), findsOneWidget); + await tester.enterText( + find.descendant( + of: find.byType(AlertDialog), + matching: find.byType(TextField), + ), + 'Captured from profile quick action for regression test', + ); + + await tester.tap(find.widgetWithText(FilledButton, 'Save')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 250)); + await tester.pumpAndSettle(const Duration(milliseconds: 400)); + + expect(tester.takeException(), isNull); + + final LocalDataState after = await container.read( + localRepositoryProvider.future, + ); + expect(after.moments.length, beforeMomentsCount + 1); + expect( + after.moments.first.summary, + contains('Captured from profile quick action'), + ); + }, + ); +}