import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:hepan_everyday/features/school/undergraduate/auth_page.dart'; import 'package:hepan_everyday/features/school/undergraduate/session.dart'; import 'package:hepan_everyday/features/school/undergraduate/page_probe.dart'; import 'package:hepan_everyday/app/app_controller.dart'; import 'package:hepan_everyday/domain/app_snapshot.dart'; import 'package:hepan_everyday/domain/school_term.dart'; import 'package:hepan_everyday/services/school_page_diagnostic.dart'; import '../support/fake_school_web.dart'; import '../support/fakes.dart'; import '../school/undergraduate_session_test.dart' show underEvidence; import '../domain/schedule_engine_test.dart' show course; void main() { late FakeSchoolPlatform platform; late UndergraduateSession session; late AppController controller; late MemoryStore store; setUp(() { platform = FakeSchoolPlatform(); WebViewPlatform.instance = platform; session = UndergraduateSession(); store = MemoryStore(); }); tearDown(() { session.dispose(); controller.dispose(); }); Future open(WidgetTester tester, {String? timetableHtml}) async { controller = AppController( store, FakeNotifications(), FakeCalendar(), () => DateTime.utc(2026, 10, 14), ); await controller.initialize(reconcileServices: false); await session.openTimetable(); platform.web.javascript = (s) async => underEvidence( platform.web.url!, html: s == undergraduateCaptureScript ? timetableHtml ?? File('test/fixtures/school/undergraduate-timetable.html') .readAsStringSync() : null, ); platform.delegate.finished(platform.web.url!); await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) => Scaffold( body: TextButton( onPressed: () => Navigator.push( context, MaterialPageRoute( builder: (_) => UndergraduateAuthPage( session: session, controller: controller, ), ), ), child: const Text('打开'), ), ), ), ), ); await tester.tap(find.text('打开')); await tester.pumpAndSettle(); } testWidgets('manual importer waits for the import button and then saves', ( tester, ) async { await open(tester); expect(find.text('导入当前课表'), findsOneWidget); expect(session.pendingDraft, isNull); expect(controller.snapshot.courses, isEmpty); await tester.tap(find.text('导入当前课表')); await tester.pumpAndSettle(); expect(controller.snapshot.courses, hasLength(18)); expect(find.text('打开'), findsOneWidget); SchoolPageDiagnostic.instance.clear(); }); testWidgets('missing room imports with a visible pending-room notice', ( tester, ) async { await open( tester, timetableHtml: File('test/fixtures/school/undergraduate-timetable.html') .readAsStringSync() .replaceFirst('"教室1"', '""'), ); await tester.tap(find.text('导入当前课表')); await tester.pumpAndSettle(); expect(controller.snapshot.courses.any((c) => c.room == '教室待定'), isTrue); expect(find.textContaining('部分课程教室待定'), findsOneWidget); SchoolPageDiagnostic.instance.clear(); }); testWidgets('cross school replacement can be cancelled without saving', ( tester, ) async { store.value = AppSnapshot(courses: [course()], schoolTerm: SchoolTerm(73)); await open(tester); final previous = controller.snapshot.toJson(); await tester.tap(find.text('导入当前课表')); await tester.pump(); await tester.pump(const Duration(milliseconds: 400)); await tester.pump(const Duration(milliseconds: 400)); expect(find.text('确认你的课表'), findsOneWidget); await tester.ensureVisible(find.text('取消').last); await tester.pump(); await tester.tap(find.text('取消').last); await tester.pumpAndSettle(); expect(controller.snapshot.toJson(), previous); expect(find.text('导入当前课表'), findsOneWidget); SchoolPageDiagnostic.instance.clear(); }); testWidgets('failed saving stays on the browser and can be retried', ( tester, ) async { await open(tester); store.failSave = true; await tester.tap(find.text('导入当前课表')); await tester.pumpAndSettle(); expect(controller.snapshot.courses, isEmpty); expect(find.text('重试保存'), findsOneWidget); store.failSave = false; await tester.tap(find.text('重试保存')); await tester.pumpAndSettle(); expect(controller.snapshot.courses, hasLength(18)); SchoolPageDiagnostic.instance.clear(); }); testWidgets('an obsolete deferred save does not strand a newer import', ( tester, ) async { await open(tester); await tester.tap(find.text('导入当前课表')); await session.openTimetable(); await session.readCurrentTimetable(); platform.delegate.finished(platform.web.url!); await tester.pumpAndSettle(); expect(controller.snapshot.courses, hasLength(18)); SchoolPageDiagnostic.instance.clear(); }); }