feat: deliver Riverside Schedule seven-day grid and school HTML import

This commit is contained in:
Codex
2026-10-13 11:18:50 +08:00
parent 91a2522043
commit 8403be05da
29 changed files with 1246 additions and 446 deletions
@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.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(
'conflicts keep readable full-width metadata without covering courses',
(tester) async {
tester.view.physicalSize = const Size(320, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final term = Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1);
final courses = List.generate(
5,
(i) => Course(
id: '$i',
name: '并行课程$i',
teacher: '示例教师$i',
room: '示例教学楼B10$i',
weekday: 1,
startPeriod: 1,
endPeriod: 2,
weeks: {1},
source: CourseSource.file,
),
);
final events = ScheduleEngine.occurrences(courses, term);
await tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: MediaQueryData(textScaler: TextScaler.linear(2)),
child: Scaffold(
body: SingleChildScrollView(
child: WeekGrid(
events: events,
monday: term.firstMonday,
today: term.startDate,
settings: const AppSettings(),
),
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('冲突列表'), findsOneWidget);
expect(find.text('1–2节'), findsNWidgets(5));
final rects = <Rect>[];
for (final c in courses) {
final card = tester.getRect(
find.bySemanticsLabel('${c.name},${c.teacher},${c.room}'),
);
rects.add(card);
expect(card.width, greaterThan(30));
for (final text in [c.teacher, c.room]) {
final finder = find.text(text);
final paragraph = tester.renderObject<RenderParagraph>(finder);
for (final box in paragraph.getBoxesForSelection(
TextSelection(baseOffset: 0, extentOffset: text.length),
)) {
expect(box.right, lessThanOrEqualTo(paragraph.size.width + 0.01));
}
expect(tester.getRect(finder).bottom, lessThanOrEqualTo(card.bottom));
}
}
for (var i = 0; i < rects.length; i++) {
for (var j = i + 1; j < rects.length; j++) {
expect(
rects[i].intersect(rects[j]).height,
lessThanOrEqualTo(0.01),
reason: '${rects[i]} / ${rects[j]}',
);
}
}
expect(tester.takeException(), isNull);
},
);
testWidgets(
'a lunch-gap arrangement has the same length as another two-lesson card',
(tester) async {
final term = Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1);
Course c(String id, int day, int first) => Course(
id: id,
name: id,
teacher: '教师',
room: '教室',
weekday: day,
startPeriod: first,
endPeriod: first + 1,
weeks: {1},
source: CourseSource.file,
);
final events = ScheduleEngine.occurrences([
c('A', 1, 3),
c('B', 2, 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();
expect(
tester.getRect(find.bySemanticsLabel('A,教师,教室')).height,
tester.getRect(find.bySemanticsLabel('B,教师,教室')).height,
);
},
);
}