feat: 完善听力口语辅助状态记录、练习分块与复习抽查间隔机制

This commit is contained in:
shen
2026-09-23 18:33:22 -07:00
parent 6b42b7abc3
commit d740a9e19c
106 changed files with 2933 additions and 845 deletions
+14 -18
View File
@@ -4,9 +4,7 @@ import 'package:kouyu_english/core/app_state.dart';
import 'package:kouyu_english/core/models.dart';
import 'package:kouyu_english/core/courses/course_repository.dart';
/// The A0 → A1 → A2 → B1 unlock chain. Crossing out of A0 into A1 additionally
/// requires passing the A0 stage assessment; higher levels then chain linearly
/// (finishing the previous unit unlocks the next), with no extra assessment.
/// Draft packs are loaded for editing and validation but are not learnable.
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -72,13 +70,13 @@ void main() {
reason: 'A1 stays locked until A0 is passed',
);
// Passing the A0 stage assessment opens the first A1 unit.
// Passing the A0 stage assessment does not bypass editorial approval.
makeA0Passed(state);
expect(state.a0Passed, isTrue);
expect(state.isLessonUnlocked(firstA1.id), isTrue);
expect(state.isLessonUnlocked(firstA1.id), isFalse);
});
test('A1 → A2 → B1 chain linearly by finishing the previous unit', () {
test('draft packs stay locked even when prerequisites are complete', () {
final state = AppState();
makeA0Passed(state);
for (final lesson in a0SeedLessons) {
@@ -89,34 +87,32 @@ void main() {
final a2 = byLevel('A2');
final b1 = byLevel('B1');
// A1 opened, but only the first unit; the second waits on the first.
expect(state.isLessonUnlocked(a1.first.id), isTrue);
expect(state.isLessonUnlocked(a1.first.id), isFalse);
expect(state.isLessonUnlocked(a1[1].id), isFalse);
// Finishing the first A1 unit unlocks the second — no extra gate.
state.completedLessonIds.add(a1.first.id);
expect(state.isLessonUnlocked(a1[1].id), isTrue);
// A2 stays locked until the last A1 unit is finished.
expect(state.isLessonUnlocked(a1[1].id), isFalse);
expect(state.isLessonUnlocked(a2.first.id), isFalse);
for (final lesson in a1) {
state.completedLessonIds.add(lesson.id);
}
expect(
state.isLessonUnlocked(a2.first.id),
isTrue,
reason: 'A1 → A2 chains linearly, no separate assessment',
isFalse,
reason: 'Completing draft prerequisites must not open another draft',
);
// B1 likewise unlocks only after the last A2 unit.
expect(state.isLessonUnlocked(b1.first.id), isFalse);
for (final lesson in a2) {
state.completedLessonIds.add(lesson.id);
}
expect(
state.isLessonUnlocked(b1.first.id),
isTrue,
reason: 'A2 → B1 chains linearly',
isFalse,
reason: 'B1 remains closed until approved',
);
expect(state.nextIncompleteLessonId, isNull);
expect(state.allLessonsComplete, isTrue);
state.setPlacementStartLesson(a1.first.id);
expect(state.placementStartLessonId, 'a0-01');
});
}