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) => '').join()}${[1, 3, 5, 7, 9].map((p) => '${List.generate(7, (i) => '').join()}').join()}
节次星期$d
$p-${p == 9 ? 11 : p + 1}节${records['t${i + 1}$p'] ?? ''}
'; } 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, ); }); }