feat: integrate undergraduate school workflows and period twelve
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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/school_identity.dart';
|
||||
import 'package:hepan_everyday/domain/school_term.dart';
|
||||
import 'package:hepan_everyday/domain/schedule_engine.dart';
|
||||
import 'package:hepan_everyday/features/timetable/timetable_page.dart';
|
||||
import 'package:hepan_everyday/features/timetable/week_grid.dart';
|
||||
import 'package:hepan_everyday/services/local_notification_service.dart';
|
||||
import 'package:hepan_everyday/services/android_calendar_service.dart';
|
||||
|
||||
import '../support/fakes.dart';
|
||||
import '../domain/schedule_engine_test.dart' show course;
|
||||
import '../services/notification_service_test.dart' show AlarmGateway;
|
||||
import '../services/calendar_service_test.dart' show CalendarProvider;
|
||||
|
||||
void main() {
|
||||
setUpAll(tzdata.initializeTimeZones);
|
||||
testWidgets('row count follows imported school across empty weeks and demo', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(360, 800);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final controller = AppController(
|
||||
MemoryStore(),
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
() => DateTime.utc(2026, 9, 7),
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
final settings = AppSettings(
|
||||
semester: Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 20),
|
||||
);
|
||||
controller.snapshot = AppSnapshot(
|
||||
settings: settings,
|
||||
schoolTerm: SchoolTerm(523, school: SupportedSchool.uestcUndergraduate),
|
||||
courses: [
|
||||
course(first: 9, last: 12, weeks: {2}),
|
||||
],
|
||||
selectedSchool: SupportedSchool.uestcGraduate,
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(body: TimetablePage(controller: controller)),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.widget<WeekGrid>(find.byType(WeekGrid)).periodCount, 12);
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(w) => w is Text && w.data == '12' && w.style?.color == null,
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(tester.getRect(find.text('周日')).right, lessThanOrEqualTo(360));
|
||||
await controller.showDemo();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(body: TimetablePage(controller: controller)),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.widget<WeekGrid>(find.byType(WeekGrid)).periodCount, 11);
|
||||
});
|
||||
test('period twelve drives reminders and calendar ending at 22:45', () async {
|
||||
final term = Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1),
|
||||
events = ScheduleEngine.occurrences([
|
||||
course(day: 1, first: 12, last: 12),
|
||||
], Semester(startDate: DateTime(2026, 9, 7), totalWeeks: 1));
|
||||
expect(events.single.start.hour, 22);
|
||||
expect(events.single.end.hour, 22);
|
||||
expect(events.single.end.minute, 45);
|
||||
final night = ScheduleEngine.occurrences([
|
||||
course(day: 1, first: 9, last: 12),
|
||||
], term).single;
|
||||
expect(night.start.hour, 19);
|
||||
expect(night.start.minute, 30);
|
||||
expect(night.end.minute, 45);
|
||||
final gateway = AlarmGateway();
|
||||
await LocalNotificationService(gateway).reconcile(
|
||||
events,
|
||||
AppSettings(semester: term, reminderOffsets: [5, 15]),
|
||||
DateTime.utc(2026, 9, 6),
|
||||
);
|
||||
expect(
|
||||
gateway.alarms.values
|
||||
.map((a) => '${a.fireAt.hour}:${a.fireAt.minute}')
|
||||
.toSet(),
|
||||
{'21:55', '21:45'},
|
||||
);
|
||||
final provider = CalendarProvider();
|
||||
await AndroidCalendarService(provider).sync(events, 'local', {});
|
||||
expect(
|
||||
provider.drafts.values.single.endMs,
|
||||
events.single.end.millisecondsSinceEpoch,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:timezone/data/latest.dart' as tzdata;
|
||||
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';
|
||||
import 'package:hepan_everyday/app/hepan_app.dart';
|
||||
import 'package:hepan_everyday/app/app_controller.dart';
|
||||
import 'package:hepan_everyday/domain/app_snapshot.dart';
|
||||
import 'package:hepan_everyday/domain/school_identity.dart';
|
||||
import 'package:hepan_everyday/domain/exam_schedule.dart';
|
||||
import 'package:hepan_everyday/domain/undergraduate_exam_query.dart';
|
||||
import 'package:hepan_everyday/features/school/school_session.dart';
|
||||
import 'package:hepan_everyday/features/school/undergraduate/session.dart';
|
||||
|
||||
import '../support/fakes.dart';
|
||||
import '../support/fake_school_web.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(tzdata.initializeTimeZones);
|
||||
testWidgets(
|
||||
'undergraduate selection persists and enters its manual workflow',
|
||||
(tester) async {
|
||||
final platform = FakeSchoolPlatform();
|
||||
WebViewPlatform.instance = platform;
|
||||
final store = MemoryStore(),
|
||||
controller = AppController(
|
||||
MemoryStore(),
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
() => DateTime.utc(2026, 10, 14),
|
||||
);
|
||||
final under = UndergraduateSession();
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(under.dispose);
|
||||
await controller.initialize();
|
||||
await tester.pumpWidget(
|
||||
HepanApp(
|
||||
controller: controller,
|
||||
undergraduate: under,
|
||||
undergraduateBuilder: () => const Scaffold(body: Text('本科网页')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(platform.controllerCreations, 0);
|
||||
await tester.tap(find.bySemanticsLabel('个人设置'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, '选择学校'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('电子科技大学(研究生)'), findsOneWidget);
|
||||
expect(find.text('电子科技大学(本科生)'), findsOneWidget);
|
||||
await tester.tap(find.text('电子科技大学(本科生)'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('本科网页'), findsOneWidget);
|
||||
expect(
|
||||
controller.snapshot.selectedSchool,
|
||||
SupportedSchool.uestcUndergraduate,
|
||||
);
|
||||
expect(platform.web.loads, ['https://eams.uestc.edu.cn/eams/']);
|
||||
expect(under.requestingExams, isFalse);
|
||||
store.value = AppSnapshot.fromJson(controller.snapshot.toJson());
|
||||
expect(store.value!.selectedSchool, SupportedSchool.uestcUndergraduate);
|
||||
},
|
||||
);
|
||||
testWidgets('failed school preference does not create a browser', (
|
||||
tester,
|
||||
) async {
|
||||
final platform = FakeSchoolPlatform();
|
||||
WebViewPlatform.instance = platform;
|
||||
final store = MemoryStore(), under = UndergraduateSession();
|
||||
final controller = AppController(
|
||||
store,
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
DateTime.now,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(under.dispose);
|
||||
await controller.initialize();
|
||||
store.failSave = true;
|
||||
await tester.pumpWidget(
|
||||
HepanApp(
|
||||
controller: controller,
|
||||
undergraduate: under,
|
||||
undergraduateBuilder: () => const Scaffold(body: Text('本科网页')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.bySemanticsLabel('个人设置'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, '选择学校'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('电子科技大学(本科生)'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(platform.controllerCreations, 0);
|
||||
expect(controller.snapshot.selectedSchool, isNull);
|
||||
expect(find.text('本科网页'), findsNothing);
|
||||
});
|
||||
testWidgets('exam refresh reuses the successful undergraduate query', (
|
||||
tester,
|
||||
) async {
|
||||
final platform = FakeSchoolPlatform();
|
||||
WebViewPlatform.instance = platform;
|
||||
final under = UndergraduateSession();
|
||||
final store = MemoryStore(
|
||||
AppSnapshot(
|
||||
selectedSchool: SupportedSchool.uestcUndergraduate,
|
||||
examSchedule: ExamSchedule(
|
||||
semesterLabel: '2025-2026第二学期',
|
||||
termId: '503',
|
||||
school: SupportedSchool.uestcUndergraduate,
|
||||
requestedExamTypes: {1, 2},
|
||||
records: [],
|
||||
),
|
||||
),
|
||||
);
|
||||
final controller = AppController(
|
||||
store,
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
DateTime.now,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(under.dispose);
|
||||
await controller.initialize();
|
||||
await tester.pumpWidget(
|
||||
HepanApp(
|
||||
controller: controller,
|
||||
undergraduate: under,
|
||||
undergraduateBuilder: () => const Scaffold(body: Text('本科网页')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.bySemanticsLabel('考试'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byTooltip('从学校更新考试'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('本科网页'), findsOneWidget);
|
||||
expect(under.query!.term.id, 503);
|
||||
expect(under.query!.types, {
|
||||
UndergraduateExamType.finalExam,
|
||||
UndergraduateExamType.midterm,
|
||||
});
|
||||
});
|
||||
testWidgets('query option cancellation starts no login', (tester) async {
|
||||
final platform = FakeSchoolPlatform();
|
||||
WebViewPlatform.instance = platform;
|
||||
final under = UndergraduateSession();
|
||||
final controller = AppController(
|
||||
MemoryStore(
|
||||
AppSnapshot(selectedSchool: SupportedSchool.uestcUndergraduate),
|
||||
),
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
DateTime.now,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(under.dispose);
|
||||
await controller.initialize();
|
||||
await tester.pumpWidget(
|
||||
HepanApp(
|
||||
controller: controller,
|
||||
undergraduate: under,
|
||||
undergraduateBuilder: () => const Scaffold(body: Text('本科网页')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.bySemanticsLabel('考试'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byTooltip('查询选项'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('取消'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(platform.controllerCreations, 0);
|
||||
});
|
||||
testWidgets(
|
||||
'shared logout invalidates both schools without creating another browser',
|
||||
(tester) async {
|
||||
final platform = FakeSchoolPlatform();
|
||||
WebViewPlatform.instance = platform;
|
||||
final under = UndergraduateSession(), grad = SchoolSession();
|
||||
await grad.openTimetable();
|
||||
await under.openTimetable();
|
||||
final controller = AppController(
|
||||
MemoryStore(
|
||||
AppSnapshot(selectedSchool: SupportedSchool.uestcUndergraduate),
|
||||
),
|
||||
FakeNotifications(),
|
||||
FakeCalendar(),
|
||||
DateTime.now,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
addTearDown(under.dispose);
|
||||
addTearDown(grad.dispose);
|
||||
await controller.initialize();
|
||||
await tester.pumpWidget(
|
||||
HepanApp(controller: controller, school: grad, undergraduate: under),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
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(platform.controllerCreations, 2);
|
||||
expect(under.canLogout, isFalse);
|
||||
expect(grad.canLogout, isFalse);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user