feat: add two-tab timetable UI and glass dock

This commit is contained in:
Codex
2026-10-12 23:33:33 +08:00
parent 808af007a2
commit 8a98d49d12
17 changed files with 1912 additions and 9 deletions
+56
View File
@@ -0,0 +1,56 @@
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/hepan_app.dart';
import 'package:hepan_everyday/app/app_controller.dart';
import '../support/fakes.dart';
AppController uiController() => AppController(
MemoryStore(),
FakeNotifications(),
FakeCalendar(),
() => DateTime.utc(2026, 10, 12),
);
void main() {
setUpAll(tzdata.initializeTimeZones);
testWidgets('two icon tabs navigate to profile and timetable', (
tester,
) async {
final controller = uiController();
await controller.initialize();
await tester.pumpWidget(HepanApp(controller: controller));
await tester.pumpAndSettle();
expect(find.bySemanticsLabel('我的'), findsOneWidget);
await tester.tap(find.bySemanticsLabel('我的'));
await tester.pumpAndSettle();
expect(find.text('登录学校账户'), findsOneWidget);
expect(find.text('课表设置'), findsOneWidget);
await tester.tap(find.bySemanticsLabel('课表'));
await tester.pumpAndSettle();
expect(find.text('体验示例课表'), findsOneWidget);
controller.dispose();
});
testWidgets('demo reaches Sunday and the last lesson on a small display', (
tester,
) async {
tester.view.physicalSize = const Size(360, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final controller = uiController();
await controller.initialize();
await controller.showDemo();
await tester.pumpWidget(HepanApp(controller: controller));
await tester.pumpAndSettle();
expect(find.text('示例课表'), findsOneWidget);
expect(find.text('11'), findsOneWidget);
await tester.ensureVisible(find.text('周一'));
await tester.drag(find.text('周一'), const Offset(-650, 0));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
expect(find.text('周日'), findsOneWidget);
expect(tester.getCenter(find.text('周日')).dx, lessThan(360));
controller.dispose();
});
}
+59
View File
@@ -0,0 +1,59 @@
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/hepan_app.dart';
import 'navigation_test.dart' show uiController;
void main() {
setUpAll(tzdata.initializeTimeZones);
testWidgets(
'invalid semester weeks are rejected without changing saved data',
(tester) async {
final controller = uiController();
await controller.initialize();
await tester.pumpWidget(HepanApp(controller: controller));
await tester.pumpAndSettle();
expect(find.bySemanticsLabel('我的'), findsOneWidget);
await tester.tap(find.bySemanticsLabel('我的'));
await tester.pumpAndSettle();
await tester.ensureVisible(find.text('课表设置'));
await tester.pumpAndSettle();
await tester.tap(find.text('课表设置'));
await tester.pumpAndSettle();
await tester.scrollUntilVisible(
find.byKey(const Key('semester-weeks')),
300,
scrollable: find.byType(Scrollable).last,
);
await tester.enterText(find.byKey(const Key('semester-weeks')), '31');
await tester.ensureVisible(find.text('保存学期设置'));
await tester.tap(find.text('保存学期设置'));
await tester.pumpAndSettle();
expect(find.text('周数应为 1–30'), findsOneWidget);
expect(controller.snapshot.settings.semester, isNull);
controller.dispose();
},
);
testWidgets('two hundred percent font demo has no overflow', (tester) async {
tester.view.physicalSize = const Size(360, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
tester.platformDispatcher.textScaleFactorTestValue = 2;
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
final controller = uiController();
await controller.initialize();
await controller.showDemo();
await tester.pumpWidget(HepanApp(controller: controller));
await tester.pumpAndSettle();
await tester.scrollUntilVisible(
find.text('示例课表'),
250,
scrollable: find.byType(Scrollable).first,
);
expect(find.text('示例课表'), findsOneWidget);
expect(tester.takeException(), isNull);
controller.dispose();
});
}
+51
View File
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:timezone/data/latest.dart' as tzdata;
import 'package:hepan_everyday/features/timetable/week_grid.dart';
import 'package:hepan_everyday/domain/schedule_engine.dart';
import 'package:hepan_everyday/domain/app_settings.dart';
void main() {
setUpAll(tzdata.initializeTimeZones);
testWidgets('transitive lesson conflicts never cover another course card', (
tester,
) async {
final term = Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1);
Course c(String name, int first, int last) => Course(
id: name,
name: name,
teacher: 'T',
room: 'R',
weekday: 1,
startPeriod: first,
endPeriod: last,
weeks: {1},
source: CourseSource.file,
);
final events = ScheduleEngine.occurrences([
c('A', 1, 2),
c('B', 2, 3),
c('C', 3, 4),
], term);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: WeekGrid(
events: events,
monday: term.firstMonday,
today: term.startDate,
settings: const AppSettings(),
),
),
),
),
);
await tester.pumpAndSettle();
final a = tester.getRect(find.bySemanticsLabel('A,T,R'));
final b = tester.getRect(find.bySemanticsLabel('B,T,R'));
final cRect = tester.getRect(find.bySemanticsLabel('C,T,R'));
expect(a.right, lessThanOrEqualTo(b.left));
expect(b.right, lessThanOrEqualTo(cRect.left));
});
}