Files
English/kouyu_english/test/course_repository_test.dart

133 lines
4.9 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:kouyu_english/core/courses/courses.dart';
import 'package:kouyu_english/core/courses/course_repository.dart';
List<SeedLesson> get loadedLessons =>
allLessons.where((lesson) => lesson.level != 'A0').toList();
List<DialogueScene> get loadedScenes =>
allScenes.where((scene) => !scene.id.startsWith('a0-')).toList();
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() => CourseRepository.instance.resetForTest());
tearDown(() => CourseRepository.instance.resetForTest());
test('loads every unit and adapts it into the runtime registries', () async {
await CourseRepository.instance.load();
final units = CourseRepository.instance.units;
// The course map lists 10 A0 + 21 A1 + 15 A2 + 14 B1 units.
expect(units.length, 60);
expect(units.where((u) => u.level == 'A0').length, 10);
expect(units.where((u) => u.level == 'A1').length, 21);
expect(units.where((u) => u.level == 'A2').length, 15);
expect(units.where((u) => u.level == 'B1').length, 14);
// Unit ids are unique.
expect(units.map((u) => u.id).toSet().length, units.length);
// Adapted lessons are appended after A0 with continuous numbering.
expect(loadedLessons.length, 50);
expect(loadedLessons.first.number, a0SeedLessons.length + 1);
expect(loadedLessons.last.number, a0SeedLessons.length + 50);
expect(allLessons.length, a0SeedLessons.length + 50);
for (final lesson in loadedLessons) {
expect(
lesson.segments,
isNotEmpty,
reason: '${lesson.id} has no segments',
);
expect(lesson.targetItemIds, isNotEmpty);
expect(lesson.vocabulary, isNotEmpty);
// Every adapted lesson resolves through the shared accessors.
expect(lessonById(lesson.id).id, lesson.id);
for (final segment in lesson.segments) {
expect(findLessonSegment(segment.id)?.id, segment.id);
final activity = activityBySegmentId(segment.id);
expect(
activity.listening.isNotEmpty || activity.reading.isNotEmpty,
isTrue,
reason: '${segment.id} has empty listening and reading',
);
}
}
});
test(
'core items register their labels, mastery flag and match rules',
() async {
await CourseRepository.instance.load();
final unit = CourseRepository.instance.unitById('A1-U01')!;
expect(unit.coreItems, isNotEmpty);
for (final item in unit.coreItems) {
expect(isCoreItem(item.id), isTrue);
expect(coreItemLabel(item.id), item.zh);
expect(coreItemEnglish(item.id), item.en);
expect(coreItemMatch[item.id], item.match);
expect(coreReviewTemplates[item.id], isNotNull);
}
},
);
test('each unit main scene becomes a free-practice scene', () async {
await CourseRepository.instance.load();
final expected = CourseRepository.instance.units
.where((u) => u.level != 'A0' && u.mainScene != null)
.length;
expect(loadedScenes.length, expected);
for (final scene in loadedScenes) {
expect(scene.unlockAfterLessonId, isNotNull);
expect(scene.script.prompts, isNotEmpty);
// The scene is reachable through the shared accessor.
expect(sceneById(scene.id).id, scene.id);
}
});
test('listening/reading materials parse with answerable questions', () async {
await CourseRepository.instance.load();
final withMaterials = CourseRepository.instance.units
.where((u) => u.materials.isNotEmpty)
.toList();
expect(withMaterials, isNotEmpty);
for (final unit in withMaterials) {
for (final material in unit.materials) {
expect(material.text, isNotEmpty);
for (final question in material.questions) {
expect(question.options, isNotEmpty);
// The first option is the correct answer by contract.
expect(question.answer, question.options.first);
}
}
}
});
test('load is idempotent', () async {
await CourseRepository.instance.load();
final count = loadedLessons.length;
await CourseRepository.instance.load();
expect(loadedLessons.length, count);
expect(CourseRepository.instance.units.length, 60);
});
test('AI word list covers only the recent units, in English', () async {
await CourseRepository.instance.load();
final b1 = loadedLessons.last;
final words = taughtLanguageUpTo(b1.id);
final window = loadedLessons.sublist(
loadedLessons.length - taughtLanguageUnitWindow,
);
final expected = <String>{
for (final lesson in window)
for (final id in lesson.targetItemIds) coreItemEnglish(id),
};
expect(words, expected.toList());
expect(words.any((w) => RegExp(r'[\u4e00-\u9fff]').hasMatch(w)), isFalse);
// The first A1 unit does not pull in A0.
final firstA1 = taughtLanguageUpTo(loadedLessons.first.id);
expect(firstA1.any(a0CoreItems.values.contains), isFalse);
expect(lessonLevel(b1.id), 'B1');
});
}