import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.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/reminders/scheduling/reminder_scheduler.dart'; import 'package:relationship_saver/features/reminders/scheduling/reminder_scheduler_provider.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({}); }); test('seeds local data on first load', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final state = await container.read(localRepositoryProvider.future); expect(state.people, isNotEmpty); expect(state.moments, isNotEmpty); expect(state.tasks, isNotEmpty); }); test('adds and removes person with persistence state update', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final initial = await container.read(localRepositoryProvider.future); final int initialCount = initial.people.length; await container .read(localRepositoryProvider.notifier) .addPerson( name: 'Test Person', relationship: 'Friend', notes: 'test', tags: ['alpha'], location: 'Berlin', ); final afterAdd = await container.read(localRepositoryProvider.future); expect(afterAdd.people.length, initialCount + 1); expect(afterAdd.people.first.location, 'Berlin'); final String insertedId = afterAdd.people.first.id; await container .read(localRepositoryProvider.notifier) .deletePerson(insertedId); final afterDelete = await container.read(localRepositoryProvider.future); expect(afterDelete.people.length, initialCount); }); test('adds and removes capture moments', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final state = await container.read(localRepositoryProvider.future); final String personId = state.people.first.id; final int beforeCount = state.moments.length; await container .read(localRepositoryProvider.notifier) .addMoment(personId: personId, summary: 'This is a new capture moment'); final withMoment = await container.read(localRepositoryProvider.future); expect(withMoment.moments.length, beforeCount + 1); final String momentId = withMoment.moments.first.id; await container .read(localRepositoryProvider.notifier) .deleteMoment(momentId); final afterDelete = await container.read(localRepositoryProvider.future); expect(afterDelete.moments.length, beforeCount); }); test( 'ingestSharedMessage auto-creates profile and reuses source link for follow-up messages', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final LocalDataState before = await container.read( localRepositoryProvider.future, ); final SharedMessageIngestResult first = await container .read(localRepositoryProvider.notifier) .ingestSharedMessage( const SharedMessageIngestInput( sourceApp: 'whatsapp', sourceDisplayName: 'Taylor Quinn', sourceUserId: 'wa:taylor quinn', messageText: 'Can we do coffee this week?', ), ); final LocalDataState afterFirst = await container.read( localRepositoryProvider.future, ); expect(first.createdProfile, isTrue); expect(afterFirst.people.length, before.people.length + 1); expect( afterFirst.sourceLinks.any((link) => link.profileId == first.profileId), isTrue, ); expect( afterFirst.moments.any( (RelationshipMoment moment) => moment.personId == first.profileId && moment.type == 'whatsapp', ), isTrue, ); final SharedMessageIngestResult second = await container .read(localRepositoryProvider.notifier) .ingestSharedMessage( const SharedMessageIngestInput( sourceApp: 'whatsapp', sourceDisplayName: 'Taylor Quinn', sourceUserId: 'wa:taylor quinn', messageText: 'Lets plan Sunday brunch too.', ), ); final LocalDataState afterSecond = await container.read( localRepositoryProvider.future, ); expect(second.createdProfile, isFalse); expect(second.profileId, first.profileId); expect(afterSecond.people.length, afterFirst.people.length); expect( afterSecond.sharedMessages.length, afterFirst.sharedMessages.length + 1, ); }, ); test('adds, archives, edits, and deletes ideas', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final LocalDataState state = await container.read( localRepositoryProvider.future, ); final String personId = state.people.first.id; final int beforeCount = state.ideas.length; await container .read(localRepositoryProvider.notifier) .addIdea( type: IdeaType.gift, title: 'Test idea', details: 'some details', personId: personId, ); final LocalDataState afterAdd = await container.read( localRepositoryProvider.future, ); expect(afterAdd.ideas.length, beforeCount + 1); final RelationshipIdea inserted = afterAdd.ideas.first; await container .read(localRepositoryProvider.notifier) .toggleIdeaArchived(inserted.id); LocalDataState afterArchive = await container.read( localRepositoryProvider.future, ); expect(afterArchive.ideas.first.isArchived, isTrue); await container .read(localRepositoryProvider.notifier) .updateIdea(afterArchive.ideas.first.copyWith(title: 'Updated idea')); afterArchive = await container.read(localRepositoryProvider.future); expect(afterArchive.ideas.first.title, 'Updated idea'); await container .read(localRepositoryProvider.notifier) .deleteIdea(inserted.id); final LocalDataState afterDelete = await container.read( localRepositoryProvider.future, ); expect(afterDelete.ideas.length, beforeCount); }); test('adds, updates, toggles, and deletes reminders', () async { final ProviderContainer container = _createContainer(); addTearDown(container.dispose); final LocalDataState state = await container.read( localRepositoryProvider.future, ); final String personId = state.people.first.id; final int beforeCount = state.reminders.length; await container .read(localRepositoryProvider.notifier) .addReminder( title: 'Test reminder', cadence: ReminderCadence.weekly, nextAt: DateTime.now().add(const Duration(days: 1)), personId: personId, ); LocalDataState afterAdd = await container.read( localRepositoryProvider.future, ); expect(afterAdd.reminders.length, beforeCount + 1); final ReminderRule inserted = afterAdd.reminders.first; await container .read(localRepositoryProvider.notifier) .toggleReminderEnabled(inserted.id); afterAdd = await container.read(localRepositoryProvider.future); expect(afterAdd.reminders.first.enabled, isFalse); await container .read(localRepositoryProvider.notifier) .updateReminder( afterAdd.reminders.first.copyWith(title: 'Updated reminder'), ); afterAdd = await container.read(localRepositoryProvider.future); expect(afterAdd.reminders.first.title, 'Updated reminder'); await container .read(localRepositoryProvider.notifier) .deleteReminder(inserted.id); final LocalDataState afterDelete = await container.read( localRepositoryProvider.future, ); expect(afterDelete.reminders.length, beforeCount); }); test('reconciles reminder schedule after state changes', () async { final RecordingReminderScheduler scheduler = RecordingReminderScheduler(); final ProviderContainer container = _createContainer(scheduler: scheduler); addTearDown(container.dispose); final LocalDataState initial = await container.read( localRepositoryProvider.future, ); final int beforeCount = initial.reminders.length; await container .read(localRepositoryProvider.notifier) .addReminder( title: 'Scheduled reminder', cadence: ReminderCadence.daily, nextAt: DateTime.now().add(const Duration(hours: 4)), ); final LocalDataState afterAdd = await container.read( localRepositoryProvider.future, ); await container .read(localRepositoryProvider.notifier) .deleteReminder(afterAdd.reminders.first.id); final LocalDataState afterDelete = await container.read( localRepositoryProvider.future, ); expect(scheduler.snapshots, isNotEmpty); expect(scheduler.snapshots.first.length, beforeCount); expect( scheduler.snapshots.any( (List r) => r.length == beforeCount + 1, ), isTrue, ); expect(scheduler.snapshots.last.length, afterDelete.reminders.length); }); } ProviderContainer _createContainer({ReminderScheduler? scheduler}) { return ProviderContainer( overrides: [ localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()), syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()), if (scheduler != null) reminderSchedulerProvider.overrideWithValue(scheduler), ], ); } class RecordingReminderScheduler implements ReminderScheduler { final List> snapshots = >[]; @override Future clearAll() async {} @override Future requestPermissions() async => true; @override Future reconcile(List reminders) async { snapshots.add(List.unmodifiable(reminders)); } }