import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:timezone/data/latest.dart' as tzdata; import 'package:hepan_everyday/app/app_controller.dart'; import 'package:hepan_everyday/main.dart'; import 'package:hepan_everyday/data/file_snapshot_store.dart'; import 'package:hepan_everyday/data/json_schedule_import.dart'; import 'package:hepan_everyday/domain/app_settings.dart'; import 'package:hepan_everyday/domain/course.dart'; import 'package:hepan_everyday/features/profile/import_preview.dart'; import '../test/support/fakes.dart'; import '../test/domain/schedule_engine_test.dart' show course; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(tzdata.initializeTimeZones); testWidgets( 'offline import, preview cancellation, color settings and reopening preserve saved courses', (tester) async { final directory = await Directory.systemTemp.createTemp( 'hepan-device-test-', ); final store = FileSnapshotStore(directory); final controller = AppController( store, FakeNotifications(), FakeCalendar(), () => DateTime.utc(2026, 9, 9), ); await controller.initialize(); await tester.pumpWidget(buildApplication(controller)); await tester.pumpAndSettle(); expect(find.bySemanticsLabel('课表'), findsOneWidget); await tester.tap(find.bySemanticsLabel('我的')); await tester.pumpAndSettle(); await tester.tap(find.text('课表设置')); await tester.pumpAndSettle(); await tester.tap(find.text('单色')); await tester.pumpAndSettle(); expect(controller.snapshot.settings.colorMode, ColorMode.single); // Native file chooser itself is verified manually; preview uses the same UI as file import. await tester.scrollUntilVisible(find.byKey(const Key('semester-weeks')),300, scrollable:find.byType(Scrollable).last); await tester.ensureVisible(find.text('学期开始日')); await tester.pumpAndSettle(); await tester.tap(find.text('学期开始日'));await tester.pumpAndSettle(); await tester.tap(find.text('确定'));await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key('semester-weeks')),'5'); await tester.ensureVisible(find.text('保存学期设置')); await tester.tap(find.text('保存学期设置'));await tester.pumpAndSettle(); expect(controller.snapshot.settings.semester!.totalWeeks,5); expect(controller.snapshot.settings.semester!.startDate,DateTime.utc(2026,9,9)); final draft = ImportDraft( courses: [course(last: 2)], explicitlyEmpty: false, source: CourseSource.file, parserVersion: 'device-json-v1', ); await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) => Scaffold( body: TextButton( onPressed: () => showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => ImportPreview(draft: draft, controller: controller), ), child: const Text('预览文件'), ), ), ), ), ); await tester.pumpAndSettle(); await tester.tap(find.text('预览文件')); await tester.pumpAndSettle(); await tester.tap(find.text('取消')); await tester.pumpAndSettle(); expect(controller.snapshot.courses, isEmpty); await tester.tap(find.text('预览文件')); await tester.pumpAndSettle(); await tester.tap(find.text('确认导入')); await tester.pumpAndSettle(); expect(controller.snapshot.courses.single.id, 'math'); await tester.pumpWidget(buildApplication(controller)); await tester.pumpAndSettle(); expect(find.text('矩阵论'), findsOneWidget); final before = await File('${directory.path}/hepan_snapshot.json') .readAsString(); await controller.showDemo(); await tester.pumpAndSettle(); expect(find.text('示例课表'), findsOneWidget); expect( await File('${directory.path}/hepan_snapshot.json').readAsString(), before, ); await controller.applyImport( const ImportDraft( courses: [], explicitlyEmpty: false, source: CourseSource.file, parserVersion: 'bad', ), confirmedEmpty: false, ); expect(controller.snapshot.courses.single.id, 'math'); final reopened = AppController( store, FakeNotifications(), FakeCalendar(), controller.clock, ); await reopened.initialize(); expect(reopened.snapshot.courses.single.id, 'math'); expect(reopened.snapshot.settings.colorMode, ColorMode.single); await tester.pumpWidget(const SizedBox()); controller.dispose(); reopened.dispose(); await directory.delete(recursive: true); }, ); }