import 'dart:io'; 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/data/file_snapshot_store.dart'; import 'package:hepan_everyday/domain/app_snapshot.dart'; import 'package:hepan_everyday/features/profile/settings_page.dart'; import 'package:hepan_everyday/features/timetable/week_grid.dart'; import 'package:hepan_everyday/domain/schedule_engine.dart'; import '../support/fakes.dart'; import '../domain/schedule_engine_test.dart' show course; void main() { setUpAll(tzdata.initializeTimeZones); test('old saved preferences gain longer default and malformed length is rejected', () { final old = AppSnapshot().toJson(); (old['settings'] as Map).remove('courseHeightScale'); expect( (AppSnapshot.fromJson(old).toJson()['settings'] as Map)['courseHeightScale'], 1.25, ); for (final value in [null, 'long', 0.5, 2.5, double.nan, double.infinity]) { final json = AppSnapshot().toJson(); (json['settings'] as Map)['courseHeightScale'] = value; expect(() => AppSnapshot.fromJson(json), throwsFormatException); } }); test('custom course length survives an actual disk reload', () async { final directory = await Directory.systemTemp.createTemp( 'riverside-length-', ); try { final json = AppSnapshot().toJson(); (json['settings'] as Map)['courseHeightScale'] = 1.8; await FileSnapshotStore(directory).save(AppSnapshot.fromJson(json)); final restored = await FileSnapshotStore(directory).load(); expect((restored!.toJson()['settings'] as Map)['courseHeightScale'], 1.8); } finally { await directory.delete(recursive: true); } }); testWidgets( 'length slider persists across reopening and increases equal-duration bars', (tester) async { final store = MemoryStore(); final term = Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1); final json = AppSnapshot( courses: [ course(id: 'A', day: 1, first: 3, last: 4), course(id: 'B', day: 7, first: 3, last: 4), ], ).toJson(); (json['settings'] as Map)['semester'] = { 'startDate': '2026-09-07', 'totalWeeks': 1, }; await store.save(AppSnapshot.fromJson(json)); final c = AppController( store, FakeNotifications(), FakeCalendar(), () => DateTime.utc(2026, 9, 7), ); await c.initialize(); Future barHeight(AppController controller) async { final events = ScheduleEngine.occurrences( controller.snapshot.courses, term, ); await tester.pumpWidget( MaterialApp( home: Scaffold( body: SingleChildScrollView( child: WeekGrid( events: events, monday: term.firstMonday, today: term.startDate, settings: controller.snapshot.settings, ), ), ), ), ); await tester.pumpAndSettle(); final a = tester.getRect(find.bySemanticsLabel('矩阵论,示例教师,示例教室').first), b = tester.getRect(find.bySemanticsLabel('矩阵论,示例教师,示例教室').last); expect(a.height, b.height); expect(tester.getRect(find.text('周日')).right, lessThanOrEqualTo(800)); return a.height; } final before = await barHeight(c); final alarmsBefore = (c.notifications as FakeNotifications).reconciliations; final calendarsBefore = (c.calendar as FakeCalendar).synchronizations; await tester.pumpWidget(MaterialApp(home: SettingsPage(controller: c))); await tester.pumpAndSettle(); final slider = find.byKey(const Key('course-height-slider')); await tester.ensureVisible(slider); await tester.pumpAndSettle(); final rect = tester.getRect(slider); await tester.dragFrom( Offset(rect.left + rect.width * 0.4, rect.center.dy), Offset(rect.width * 0.4, 0), ); await tester.pumpAndSettle(); final value = (c.snapshot.toJson()['settings'] as Map)['courseHeightScale'] as double; expect(value, greaterThan(1.25)); expect( (c.notifications as FakeNotifications).reconciliations, alarmsBefore, ); expect((c.calendar as FakeCalendar).synchronizations, calendarsBefore); final restarted = AppController( store, FakeNotifications(), FakeCalendar(), () => DateTime.utc(2026, 9, 7), ); await restarted.initialize(); expect( (restarted.snapshot.toJson()['settings'] as Map)['courseHeightScale'], value, ); expect(await barHeight(restarted), greaterThan(before)); await tester.pumpWidget( MaterialApp(home: SettingsPage(controller: restarted)), ); await tester.pumpAndSettle(); await tester.ensureVisible(find.text('恢复默认')); await tester.tap(find.text('恢复默认')); await tester.pumpAndSettle(); expect( (restarted.snapshot.toJson()['settings'] as Map)['courseHeightScale'], 1.25, ); expect(await barHeight(restarted), before); expect(tester.takeException(), isNull); c.dispose(); restarted.dispose(); }, ); }