import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:hepan_everyday/features/school/school_page_adapter.dart';
import 'package:hepan_everyday/domain/course.dart';
String fixture() {
const records = {
't51': '101/示例集成电路设计(1班)/32/2/示例教师甲/1-3周/1~2节/示例楼B212
101/示例集成电路设计(1班)/32/2/示例教师甲/6-6周/1~2节/示例楼B212
101/示例集成电路设计(1班)/32/2/示例教师甲/7-9周/1~2节/示例楼B212',
't13': '101/示例集成电路设计(1班)/32/2/示例教师甲/1-5周/3~4节/示例楼B212',
't23': '102/示例信号检测与估计(2班)/32/2/示例教师乙、示例教师丙/1-5周/3~4节/示例楼B102
102/示例信号检测与估计(2班)/32/2/示例教师乙、示例教师丙/7-9周/3~4节/示例楼B102',
't29': '103/示例现代化理论与实践(3班)/36/2/示例教师丁/1-12周/9~11节/示例楼B107',
};
return '
| 节次 | ${['一', '二', '三', '四', '五', '六', '日'].map((d) => '星期$d | ').join()}
${[1, 3, 5, 7, 9].map((p) => '| $p-${p == 9 ? 11 : p + 1}节 | ${List.generate(7, (i) => '${records['t${i + 1}$p'] ?? ''} | ').join()}
').join()}
';
}
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().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);
final friday = draft.courses.singleWhere((c) => c.weekday == 5);
expect(friday.weeks, {1, 2, 3, 6, 7, 8, 9});
final signal = draft.courses.singleWhere(
(c) => c.weekday == 2 && c.startPeriod == 3,
);
expect(signal.teacher, '示例教师乙、示例教师丙');
expect(signal.room, '示例楼B102');
final evening = draft.courses.singleWhere((c) => c.startPeriod == 9);
expect(evening.endPeriod, 11);
expect(draft.source, CourseSource.school);
expect(draft.explicitlyEmpty, isFalse);
expect(
SchoolPageAdapter().extract(fixture(), url).courses.map((c) => c.id),
draft.courses.map((c) => c.id),
);
});
test('invalid cell, missing weekday, mismatched period and empty grid fail closed', () {
for (final body in [
fixture().replaceFirst('1-3周', '未知周'),
fixture().replaceFirst('id="t71"', 'id="missing"'),
fixture().replaceFirst('1~2节', '5~6节'),
fixture().replaceAll(RegExp(r'\d+/示例[^<]+'), ''),
fixture().replaceFirst('星期日', '星期八'),
]) {
expect(
() => SchoolPageAdapter().extract(body, url),
throwsA(isA()),
);
}
});
test('saved HTML uses file provenance and does not authenticate a school session', () {
final draft = SchoolPageAdapter().extract(
fixture(),
url,
source: CourseSource.file,
);
expect(draft.source, CourseSource.file);
expect(draft.courses.every((c) => c.source == CourseSource.file), isTrue);
});
test('actual attached HTML imports eleven arrangements (local optional verification)', () {
final file = File(
Platform.environment['RIVERSIDE_HTML_FIXTURE'] ?? 'not-present',
);
if (!file.existsSync()) return;
final draft = SchoolPageAdapter().extract(file.readAsStringSync(), url);
expect(draft.courses.length, 11);
expect(draft.courses.map((c) => c.name).toSet().length, 7);
expect(
draft.courses
.singleWhere((c) => c.weekday == 2 && c.startPeriod == 9)
.endPeriod,
11,
);
expect(
draft.courses
.singleWhere((c) => c.weekday == 2 && c.startPeriod == 3)
.teacher
.split('、')
.length,
2,
);
});
}