import 'package:bible_tracker/data/bible.dart'; import 'package:bible_tracker/models/tracker.dart'; import 'package:bible_tracker/services/tracker_service.dart'; import 'package:bible_tracker/services/tracker_storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; Future _container() async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); return ProviderContainer( overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], ); } Future> _waitForData(ProviderContainer container) async { while (true) { final AsyncValue> state = container.read( trackerServiceProvider, ); if (state.hasValue) return state.value!; await Future.delayed(const Duration(milliseconds: 5)); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('starts empty after initial load', () async { final container = await _container(); addTearDown(container.dispose); final List data = await _waitForData(container); expect(data, isEmpty); }); test('create adds a tracker with the given name and color', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final tracker = await container .read(trackerServiceProvider.notifier) .create(name: 'My Plan', color: const Color(0xFF123456)); expect(tracker.name, 'My Plan'); expect(tracker.colorValue, const Color(0xFF123456).toARGB32()); expect(container.read(trackerServiceProvider).value, [tracker]); }); test('toggleChapter adds and then removes chapter', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final service = container.read(trackerServiceProvider.notifier); final tracker = await service.create( name: 'Plan', color: const Color(0xFF000000), ); await service.toggleChapter(tracker.id, 0, 1); Tracker current = container.read(trackerServiceProvider).value!.single; expect(current.readChapters, {Bible.chapterKey(0, 1)}); await service.toggleChapter(tracker.id, 0, 1); current = container.read(trackerServiceProvider).value!.single; expect(current.readChapters, isEmpty); }); test('setBookRead marks all chapters of the book', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final service = container.read(trackerServiceProvider.notifier); final tracker = await service.create( name: 'Plan', color: const Color(0xFF000000), ); await service.setBookRead(tracker.id, 0, read: true); final Tracker current = container .read(trackerServiceProvider) .value! .single; final Book genesis = Bible.books[0]; expect(current.readChapters.length, genesis.chapterCount); for (int chapter = 1; chapter <= genesis.chapterCount; chapter++) { expect(current.readChapters.contains(Bible.chapterKey(0, chapter)), true); } }); test('reset clears only the target tracker', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final service = container.read(trackerServiceProvider.notifier); final a = await service.create(name: 'A', color: const Color(0xFF111111)); final b = await service.create(name: 'B', color: const Color(0xFF222222)); await service.toggleChapter(a.id, 0, 1); await service.toggleChapter(b.id, 0, 1); await service.reset(a.id); final List current = container.read(trackerServiceProvider).value!; expect( current.firstWhere((Tracker t) => t.id == a.id).readChapters, isEmpty, ); expect( current.firstWhere((Tracker t) => t.id == b.id).readChapters, {Bible.chapterKey(0, 1)}, ); }); test('delete removes the tracker', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final service = container.read(trackerServiceProvider.notifier); final a = await service.create(name: 'A', color: const Color(0xFF111111)); await service.create(name: 'B', color: const Color(0xFF222222)); await service.delete(a.id); final List current = container.read(trackerServiceProvider).value!; expect(current.length, 1); expect(current.single.name, 'B'); }); test('state is persisted across container instances', () async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final first = ProviderContainer( overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], ); await _waitForData(first); final created = await first .read(trackerServiceProvider.notifier) .create(name: 'Persisted', color: const Color(0xFFAABBCC)); await first .read(trackerServiceProvider.notifier) .toggleChapter(created.id, 5, 3); first.dispose(); final second = ProviderContainer( overrides: [sharedPreferencesProvider.overrideWithValue(prefs)], ); addTearDown(second.dispose); final List loaded = await _waitForData(second); expect(loaded.length, 1); expect(loaded.single.name, 'Persisted'); expect(loaded.single.readChapters, {Bible.chapterKey(5, 3)}); }); test('updateMeta changes name and color', () async { final container = await _container(); addTearDown(container.dispose); await _waitForData(container); final service = container.read(trackerServiceProvider.notifier); final tracker = await service.create( name: 'Old', color: const Color(0xFF111111), ); await service.updateMeta( tracker.id, name: 'New', color: const Color(0xFF222222), ); final Tracker current = container .read(trackerServiceProvider) .value! .single; expect(current.name, 'New'); expect(current.colorValue, const Color(0xFF222222).toARGB32()); }); }