Preserve courses with pending rooms and report precise graduate import errors
This commit is contained in:
@@ -16,6 +16,31 @@ String fixture() {
|
||||
|
||||
void main() {
|
||||
final url = Uri.parse('https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1');
|
||||
test('graduate course identifiers need not be numeric and missing rooms remain explicit', () {
|
||||
final body = fixture()
|
||||
.replaceAll('101/示例', 'G101/示例')
|
||||
.replaceAll('/示例楼B212', '/');
|
||||
final draft = SchoolPageAdapter().extract(body, url);
|
||||
expect(draft.courses, hasLength(4));
|
||||
final pending = draft.courses.where((c) => c.name.contains('集成电路'));
|
||||
expect(pending.every((c) => c.room == '教室待定'), isTrue);
|
||||
expect(pending.every((c) => c.notes.contains('学校未提供教室')), isTrue);
|
||||
});
|
||||
test('graduate malformed record provides a safe specific reason', () {
|
||||
expect(
|
||||
() => SchoolPageAdapter().extract(
|
||||
fixture().replaceFirst('1-3周', '未知周'),
|
||||
url,
|
||||
),
|
||||
throwsA(
|
||||
isA<UnsupportedSchoolLayout>().having(
|
||||
(e) => e.toString(),
|
||||
'reason',
|
||||
contains('上课周次'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
test('school DOM merges week segments without losing full metadata or evening periods', () {
|
||||
final draft = SchoolPageAdapter().extract(fixture(), url);
|
||||
expect(draft.courses.length, 4);
|
||||
|
||||
@@ -31,6 +31,59 @@ void main() {
|
||||
platform.delegate.finished(url);
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'graduate parser rejection keeps its reason instead of becoming a network timeout',
|
||||
(tester) async {
|
||||
addTearDown(SchoolPageDiagnostic.instance.clear);
|
||||
session.connectionState = SchoolConnectionState.connected;
|
||||
await session.openTimetable(term: SchoolTerm(73));
|
||||
platform.web.javascript = (_) async => schoolPageEvidence(
|
||||
topUrl: platform.web.url!,
|
||||
html: fixture().replaceFirst('1-3周', '未知周'),
|
||||
);
|
||||
await tester.runAsync(() async {
|
||||
navigate(SchoolTerm(73).timetableUri.toString());
|
||||
await flushSchoolEvents();
|
||||
});
|
||||
expect(session.message, contains('上课周次'));
|
||||
await tester.pump(const Duration(seconds: 46));
|
||||
expect(session.message, contains('上课周次'));
|
||||
expect(session.message, isNot(contains('查找超时')));
|
||||
expect(session.pendingDraft, isNull);
|
||||
final log = utf8.decode(await DiagnosticLog.instance.export());
|
||||
expect(log, contains('"code":"weeks_invalid"'));
|
||||
expect(log, contains('"column":5'));
|
||||
expect(log, isNot(contains('示例集成电路')));
|
||||
SchoolPageDiagnostic.instance.clear();
|
||||
},
|
||||
);
|
||||
testWidgets('a new graduate page never inherits an old parsing reason', (
|
||||
tester,
|
||||
) async {
|
||||
addTearDown(SchoolPageDiagnostic.instance.clear);
|
||||
session.connectionState = SchoolConnectionState.connected;
|
||||
await session.openTimetable(term: SchoolTerm(73));
|
||||
platform.web.javascript = (_) async => schoolPageEvidence(
|
||||
topUrl: platform.web.url!,
|
||||
html: fixture().replaceFirst('1-3周', '未知周'),
|
||||
);
|
||||
await tester.runAsync(() async {
|
||||
navigate(SchoolTerm(73).timetableUri.toString());
|
||||
await flushSchoolEvents();
|
||||
});
|
||||
expect(session.message, contains('上课周次'));
|
||||
platform.web.javascript = (_) async =>
|
||||
schoolPageEvidence(topUrl: platform.web.url!);
|
||||
await tester.runAsync(() async {
|
||||
navigate(SchoolTerm(72).timetableUri.toString());
|
||||
await flushSchoolEvents();
|
||||
});
|
||||
await tester.pump(const Duration(seconds: 46));
|
||||
expect(session.message, contains('查找超时'));
|
||||
expect(session.message, isNot(contains('上课周次')));
|
||||
SchoolPageDiagnostic.instance.clear();
|
||||
});
|
||||
|
||||
test(
|
||||
'graduate auto read retains HTML after leaving and clears on next query',
|
||||
() async {
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hepan_everyday/features/school/undergraduate/timetable_adapter.dart';
|
||||
import 'package:hepan_everyday/data/schedule_file_import.dart';
|
||||
import 'package:hepan_everyday/domain/school_identity.dart';
|
||||
import 'package:hepan_everyday/domain/course.dart';
|
||||
|
||||
void main() {
|
||||
test('missing course fields identify the failing field without disclosing values', () {
|
||||
@@ -15,7 +16,6 @@ void main() {
|
||||
'index=0*unitCount+0;table0.activities[index][table0.activities[index].length]=activity;';
|
||||
for (final entry in {
|
||||
'teacher_missing': base.replaceFirst('"教师"', '""'),
|
||||
'room_missing': base.replaceFirst('"教室"', '""'),
|
||||
'course_sequence_invalid': base.replaceFirst('课程(C.01)', '课程'),
|
||||
}.entries) {
|
||||
expect(
|
||||
@@ -51,6 +51,24 @@ void main() {
|
||||
String fixture() =>
|
||||
File('test/fixtures/school/undergraduate-timetable.html')
|
||||
.readAsStringSync();
|
||||
test('missing room retains every scheduled occurrence and clearly marks it pending', () {
|
||||
final original = UndergraduateTimetableAdapter.extract(fixture(), uri);
|
||||
final draft = UndergraduateTimetableAdapter.extract(
|
||||
fixture().replaceFirst('"教室1"', '""'),
|
||||
uri,
|
||||
);
|
||||
Set<String> occurrences(Iterable<Course> courses) => <String>{
|
||||
for (final c in courses)
|
||||
for (final w in c.weeks)
|
||||
for (var p = c.startPeriod; p <= c.endPeriod; p++)
|
||||
'${c.courseCode}/${c.teacher}/${c.weekday}/$p/$w',
|
||||
};
|
||||
expect(occurrences(draft.courses), occurrences(original.courses));
|
||||
final pending = draft.courses.where((c) => c.room == '教室待定');
|
||||
expect(pending, isNotEmpty);
|
||||
expect(pending.every((c) => c.notes.contains('学校未提供教室')), isTrue);
|
||||
expect(draft.courses.any((c) => c.room == '教室2'), isTrue);
|
||||
});
|
||||
test('undergraduate activity data preserves teachers rooms weeks and period twelve', () {
|
||||
final draft = UndergraduateTimetableAdapter.extract(fixture(), uri);
|
||||
expect(draft.schoolTerm!.id, 523);
|
||||
|
||||
Reference in New Issue
Block a user