import 'package:flutter_test/flutter_test.dart'; import 'package:timezone/data/latest.dart' as tzdata; import 'package:hepan_everyday/app/app_controller.dart'; import 'package:hepan_everyday/domain/app_snapshot.dart'; import 'package:hepan_everyday/domain/app_settings.dart'; import 'package:hepan_everyday/domain/semester.dart'; import 'package:hepan_everyday/domain/course.dart'; import 'package:hepan_everyday/data/json_schedule_import.dart'; import '../support/fakes.dart'; import '../domain/schedule_engine_test.dart' show course; void main() { setUpAll(tzdata.initializeTimeZones); late MemoryStore store; late FakeNotifications notifications; late FakeCalendar calendar; late AppController controller; setUp(() { store = MemoryStore( AppSnapshot( courses: [course()], settings: AppSettings( semester: Semester(startDate: DateTime(2026, 9, 9), totalWeeks: 5), ), ), ); notifications = FakeNotifications(); calendar = FakeCalendar(); controller = AppController( store, notifications, calendar, () => DateTime.utc(2026, 9, 8), ); }); tearDown(() => controller.dispose()); test('failed saving does not change currently visible settings', () async { await controller.initialize(); store.failSave = true; await controller.updateSettings( controller.snapshot.settings.copyWith(reminderMinutes: 30), ); expect(controller.snapshot.settings.reminderMinutes, 15); expect(controller.lastError, isNotNull); }); test('concurrent settings changes persist in user action order', () async { await controller.initialize(); await Future.wait([ controller.updateSettings( controller.snapshot.settings.copyWith(reminderMinutes: 30), ), controller.updateSettings( controller.snapshot.settings.copyWith(reminderMinutes: 60), ), ]); expect(store.value!.settings.reminderMinutes, 60); }); test('demo cannot replace real courses or create real reminders', () async { await controller.initialize(); final previous = notifications.reconciliations; await controller.showDemo(); expect(controller.isDemo, isTrue); expect(store.value!.courses.single.id, 'math'); expect(notifications.reconciliations, previous); expect(calendar.synchronizations, 0); }); test('unconfirmed empty import preserves saved timetable', () async { await controller.initialize(); await controller.applyImport( const ImportDraft( courses: [], explicitlyEmpty: false, source: CourseSource.file, parserVersion: 'json-v1', ), confirmedEmpty: false, ); expect(controller.snapshot.courses.single.id, 'math'); expect(controller.lastError, isNotNull); }); test( 'clearing courses cancels owned notifications and calendar events', () async { await controller.initialize(); await controller.clearCourses(); expect(store.value!.courses, isEmpty); expect(notifications.cancellations, 1); expect(calendar.deletions, 1); }, ); }