import 'dart:convert'; 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/school_identity.dart'; import 'package:hepan_everyday/domain/school_term.dart'; import 'package:hepan_everyday/domain/exam_schedule.dart'; import 'package:hepan_everyday/data/json_schedule_import.dart'; import '../domain/schedule_engine_test.dart' show course; import '../support/fakes.dart'; void main() { setUpAll(tzdata.initializeTimeZones); late MemoryStore store; late FakeNotifications notifications; late FakeCalendar calendar; late AppController controller; setUp(() { store = MemoryStore( AppSnapshot( courses: [course()], schoolTerm: SchoolTerm(73), settings: AppSettings( semester: Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 20), ), ), ); notifications = FakeNotifications(); calendar = FakeCalendar(); controller = AppController( store, notifications, calendar, () => DateTime.utc(2026, 9, 8), ); }); tearDown(() => controller.dispose()); ExamSchedule exams() => ExamSchedule( semesterLabel: '2025-2026第二学期', termId: '503', records: [], school: SupportedSchool.uestcUndergraduate, requestedExamTypes: {1}, ); test( 'historical undergraduate exams preserve courses and services', () async { await controller.initialize(); final before = controller.snapshot; final count = notifications.reconciliations; await controller.applyExams(exams()); expect(controller.lastError, isNull); expect(controller.snapshot.courses, before.courses); expect(controller.snapshot.schoolTerm, before.schoolTerm); expect(controller.snapshot.settings.semester, before.settings.semester); expect(notifications.reconciliations, count); expect(controller.snapshot.examSchedule!.termId, '503'); }, ); test('school selection and failed exam writes are atomic', () async { await controller.initialize(); store.failSave = true; final old = controller.snapshot.toJson(); expect( await controller.selectSchool(SupportedSchool.uestcUndergraduate), isFalse, ); expect(controller.snapshot.toJson(), old); await controller.applyExams(exams()); expect(controller.snapshot.toJson(), old); store.failSave = false; expect( await controller.selectSchool(SupportedSchool.uestcUndergraduate), isTrue, ); expect(store.value!.selectedSchool, SupportedSchool.uestcUndergraduate); }); test('undergraduate course changes preserve independent exams and reset only new term dates', () async { await controller.initialize(); await controller.applyExams(exams()); final term = SchoolTerm(523, school: SupportedSchool.uestcUndergraduate); final draft = ImportDraft( courses: [course()], explicitlyEmpty: false, source: course().source, parserVersion: 'test', schoolTerm: term, ); await controller.applyImport(draft, confirmedEmpty: false); expect(controller.snapshot.settings.semester, isNull); expect(controller.snapshot.examSchedule!.termId, '503'); await controller.updateSettings( (s) => s.copyWith( semester: Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 20), ), ); final date = controller.snapshot.settings.semester; await controller.applyImport(draft, confirmedEmpty: false); expect(controller.snapshot.settings.semester, date); }); test('JSON period twelve needs an explicit undergraduate term', () { final old = course().toJson()..['endPeriod'] = 12; final json = '{"schemaVersion":1,"courses":[${const JsonEncoder().convert(old)}]}'; expect(() => JsonScheduleImport.parse(json), throwsFormatException); }); }