import 'package:flutter/material.dart'; 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/schedule_engine.dart'; import 'package:hepan_everyday/features/timetable/timetable_page.dart'; import 'package:hepan_everyday/widgets/directional_week_transition.dart'; import '../support/fakes.dart'; AppController _controller({bool hasSemester = true}) { Course lesson(int week) => Course( id: 'week-$week', name: '第$week周课程', teacher: '测试教师', room: 'A101', weekday: 3, startPeriod: 1, endPeriod: 2, weeks: {week}, source: CourseSource.file, ); return AppController( MemoryStore( AppSnapshot( courses: [lesson(1), lesson(2), lesson(3)], settings: AppSettings( semester: hasSemester ? Semester(startDate: DateTime(2026, 10, 12), totalWeeks: 3) : null, ), ), ), FakeNotifications(), FakeCalendar(), () => DateTime.utc(2026, 10, 14), ); } Future _open(WidgetTester tester, AppController controller) async { await controller.initialize(); addTearDown(controller.dispose); await tester.pumpWidget( MaterialApp( home: Scaffold(body: TimetablePage(controller: controller)), ), ); await tester.pumpAndSettle(); } void main() { setUpAll(tzdata.initializeTimeZones); testWidgets( 'week arrows animate the grid and current week reverses direction', (tester) async { await _open(tester, _controller()); await tester.tap(find.byIcon(Icons.chevron_right_rounded)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(DirectionalWeekTransition), findsOneWidget); expect(find.text('第1周课程'), findsOneWidget); expect(find.text('第2周课程'), findsOneWidget); expect( tester .widget( find.byKey(const Key('week-transition-incoming')), ) .translation .dx, greaterThan(0), ); await tester.pumpAndSettle(); expect(find.text('第1周课程'), findsNothing); await tester.tap(find.text('本周')); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect( tester .widget( find.byKey(const Key('week-transition-incoming')), ) .translation .dx, lessThan(0), ); await tester.pumpAndSettle(); expect(find.text('第 1 周'), findsOneWidget); expect(find.text('第2周课程'), findsNothing); expect(find.text('周日'), findsOneWidget); }, ); testWidgets( 'horizontal course swipe changes week and course taps still open', (tester) async { await _open(tester, _controller()); expect(find.text('第 1 周'), findsOneWidget); expect(find.text('第1周课程'), findsOneWidget); await tester.drag(find.text('第1周课程'), const Offset(-180, 0)); await tester.pumpAndSettle(); expect(find.text('第 2 周'), findsOneWidget); expect(find.text('第1周课程'), findsNothing); expect(find.text('第2周课程'), findsOneWidget); await tester.tap(find.text('第2周课程')); await tester.pumpAndSettle(); expect(find.text('知道了'), findsOneWidget); await tester.tap(find.text('知道了')); await tester.pumpAndSettle(); await tester.drag(find.text('周三'), const Offset(180, 0)); await tester.pumpAndSettle(); expect(find.text('第 1 周'), findsOneWidget); }, ); testWidgets('one swipe advances one week and stops at semester boundaries', ( tester, ) async { await _open(tester, _controller()); await tester.drag(find.text('周三'), const Offset(300, 0)); await tester.pumpAndSettle(); expect(find.text('第 1 周'), findsOneWidget); for (final week in [2, 3, 3]) { await tester.drag(find.text('周三'), const Offset(-300, 0)); await tester.pumpAndSettle(); expect(find.text('第 $week 周'), findsOneWidget); } await tester.tap(find.text('本周')); await tester.pumpAndSettle(); expect(find.text('第 1 周'), findsOneWidget); }); testWidgets('swiping an imported timetable without a semester keeps prompt', ( tester, ) async { await _open(tester, _controller(hasSemester: false)); await tester.drag(find.byType(TimetablePage), const Offset(-180, 0)); await tester.pumpAndSettle(); expect(find.text('课表已导入,请设置学期开始日'), findsOneWidget); expect(find.text('第 2 周'), findsNothing); expect(tester.takeException(), isNull); }); }