整个课程和数据UI重新设计版本
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kouyu_english/core/courses/courses.dart';
|
||||
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.
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async => CourseRepository.instance.load());
|
||||
tearDownAll(() => CourseRepository.instance.resetForTest());
|
||||
|
||||
List<SeedLesson> byLevel(String level) =>
|
||||
allLessons.where((lesson) => lesson.level == level).toList();
|
||||
|
||||
/// Drives [state] into the "A0 passed" state: enough usable/mastered core
|
||||
/// items to sit the assessment, plus two valid passes 24h+ apart.
|
||||
void makeA0Passed(AppState state) {
|
||||
for (final id in a0CoreItems.keys.take(48)) {
|
||||
state.mastery[id] = MasteryItem(
|
||||
id: id,
|
||||
label: a0CoreItems[id]!,
|
||||
status: MasteryStatus.master,
|
||||
evidence: const [EvidenceKind.independentSuccess],
|
||||
);
|
||||
}
|
||||
final now = DateTime.now();
|
||||
final allSkillsPass = {
|
||||
for (final skill in AssessmentSkill.values) skill: true,
|
||||
};
|
||||
state.assessments
|
||||
..add(
|
||||
AssessmentRecord(
|
||||
packId: 'A0-E1',
|
||||
completedAt: now.subtract(const Duration(days: 2)),
|
||||
results: allSkillsPass,
|
||||
),
|
||||
)
|
||||
..add(
|
||||
AssessmentRecord(
|
||||
packId: 'A0-E2',
|
||||
completedAt: now.subtract(const Duration(hours: 1)),
|
||||
results: allSkillsPass,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test('the loaded content actually spans A0 → A1 → A2 → B1', () {
|
||||
expect(a0SeedLessons, isNotEmpty);
|
||||
expect(byLevel('A1'), isNotEmpty);
|
||||
expect(byLevel('A2'), isNotEmpty);
|
||||
expect(byLevel('B1'), isNotEmpty);
|
||||
// 48/30 graduation thresholds are only reachable if A0 teaches >=48 items.
|
||||
expect(a0CoreItems.length, greaterThanOrEqualTo(48));
|
||||
});
|
||||
|
||||
test('finishing A0 does not unlock A1 until the A0 assessment is passed', () {
|
||||
final state = AppState();
|
||||
final firstA1 = byLevel('A1').first;
|
||||
|
||||
// All A0 lessons done, but the stage assessment not yet passed.
|
||||
for (final lesson in a0SeedLessons) {
|
||||
state.completedLessonIds.add(lesson.id);
|
||||
}
|
||||
expect(state.a0Passed, isFalse);
|
||||
expect(
|
||||
state.isLessonUnlocked(firstA1.id),
|
||||
isFalse,
|
||||
reason: 'A1 stays locked until A0 is passed',
|
||||
);
|
||||
|
||||
// Passing the A0 stage assessment opens the first A1 unit.
|
||||
makeA0Passed(state);
|
||||
expect(state.a0Passed, isTrue);
|
||||
expect(state.isLessonUnlocked(firstA1.id), isTrue);
|
||||
});
|
||||
|
||||
test('A1 → A2 → B1 chain linearly by finishing the previous unit', () {
|
||||
final state = AppState();
|
||||
makeA0Passed(state);
|
||||
for (final lesson in a0SeedLessons) {
|
||||
state.completedLessonIds.add(lesson.id);
|
||||
}
|
||||
|
||||
final a1 = byLevel('A1');
|
||||
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[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(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',
|
||||
);
|
||||
|
||||
// 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',
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user