整个课程和数据UI重新设计版本

This commit is contained in:
shenlei
2026-09-18 17:55:44 +09:00
parent febfd30f49
commit 0a4f97d105
154 changed files with 12366 additions and 4784 deletions
+31 -27
View File
@@ -1,11 +1,9 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kouyu_english/core/app_state.dart';
import 'package:kouyu_english/core/a0_core.dart';
import 'package:kouyu_english/core/assessment_bank.dart';
import 'package:kouyu_english/core/courses/course_repository.dart';
import 'package:kouyu_english/core/courses/courses.dart';
import 'package:kouyu_english/core/generated_content.dart';
import 'package:kouyu_english/core/models.dart';
import 'package:kouyu_english/core/review_feedback.dart';
import 'package:kouyu_english/core/seed_courses.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
@@ -845,11 +843,13 @@ void main() {
test('every A0 seed lesson has complete local activities', () {
expect(a0SeedLessons, hasLength(10));
for (final lesson in a0SeedLessons) {
final activity = activityByLessonId(lesson.id);
expect(lesson.vocabulary, isNotEmpty);
expect(activity.answers, hasLength(3));
expect(activity.answers.first, isNotEmpty);
expect(activity.independentPrompt, isNotEmpty);
for (final segment in lesson.segments) {
final activity = activityBySegmentId(segment.id);
expect(activity.answers, hasLength(3));
expect(activity.answers.first, isNotEmpty);
expect(activity.independentPrompt, isNotEmpty);
}
expect(lesson.schemaVersion, '1.0');
expect(lesson.status, ContentStatus.approved);
expect(lesson.source, ContentSource.builtInOriginal);
@@ -863,7 +863,9 @@ void main() {
test('every A0 seed lesson has a practical grammar reminder', () {
for (final lesson in a0SeedLessons) {
expect(grammarNoteForLesson(lesson.id).trim(), isNotEmpty);
for (final segment in lesson.segments) {
expect(grammarNoteForSegment(segment.id).trim(), isNotEmpty);
}
}
});
@@ -892,18 +894,18 @@ void main() {
test('split lesson segments have their own reviewed activities', () {
for (final lessonId in ['a0-04', 'a0-08']) {
for (final segment in lessonById(lessonId).segments) {
expect(a0SegmentActivities, contains(segment.id));
expect(activityBySegmentId(segment.id, lessonId).listening, isNotEmpty);
expect(segmentActivities, contains(segment.id));
expect(activityBySegmentId(segment.id).listening, isNotEmpty);
}
}
expect(activityBySegmentId('a0-01-a', 'a0-01').writingPrompt, isNotEmpty);
expect(activityBySegmentId('a0-01-a').writingPrompt, isNotEmpty);
});
test('split lesson segments have their own controlled dialogues', () {
for (final lessonId in ['a0-04', 'a0-08']) {
for (final segment in lessonById(lessonId).segments) {
expect(a0SegmentDialogues, contains(segment.id));
expect(dialogueBySegmentId(segment.id, lessonId).prompts, isNotEmpty);
expect(segmentDialogues, contains(segment.id));
expect(dialogueBySegmentId(segment.id).prompts, isNotEmpty);
}
}
});
@@ -911,11 +913,11 @@ void main() {
test('split lesson segments have reviewed vocabulary and grammar notes', () {
for (final lessonId in ['a0-04', 'a0-08']) {
for (final segment in lessonById(lessonId).segments) {
final vocabulary = vocabularyBySegmentId(segment.id, lessonId);
final vocabulary = vocabularyBySegmentId(segment.id);
expect(vocabulary.map((item) => item.id), segment.previewItemIds);
expect(vocabulary.every((item) => item.meaning.isNotEmpty), isTrue);
expect(vocabulary.every((item) => item.example.isNotEmpty), isTrue);
expect(grammarNoteForSegment(segment.id, lessonId).trim(), isNotEmpty);
expect(grammarNoteForSegment(segment.id).trim(), isNotEmpty);
}
}
});
@@ -1010,7 +1012,7 @@ void main() {
test('scenes open with their lessons and rotate the recommendation', () {
final state = AppState();
expect(a0Scenes.where(state.isSceneUnlocked).map((scene) => scene.id), [
expect(allScenes.where(state.isSceneUnlocked).map((scene) => scene.id), [
'a0-meet',
]);
state.completedLessonIds.addAll(['a0-01', 'a0-02', 'a0-03', 'a0-04']);
@@ -1205,6 +1207,8 @@ void main() {
});
test('after the last lesson the learner is sent to unfinished lessons', () {
// Only A0: earlier tests' AppState.load() may have registered A1+ units.
CourseRepository.instance.resetForTest();
final state = AppState();
state.setPlacementStartLesson('a0-02');
for (var index = 1; index < a0SeedLessons.length; index++) {
@@ -1437,23 +1441,23 @@ void main() {
});
test('each lesson practises the core items it targets', () {
for (final entry in a0TargetItemIdsByLesson.entries) {
final segments = lessonById(entry.key).segments;
for (final lesson in a0SeedLessons) {
final segments = lesson.segments;
final text = [
for (final segment in segments) ...[
activityBySegmentId(segment.id, entry.key).listening,
activityBySegmentId(segment.id, entry.key).speaking,
activityBySegmentId(segment.id, entry.key).reading,
activityBySegmentId(segment.id, entry.key).writingExample,
for (final item in vocabularyBySegmentId(segment.id, entry.key))
activityBySegmentId(segment.id).listening,
activityBySegmentId(segment.id).speaking,
activityBySegmentId(segment.id).reading,
activityBySegmentId(segment.id).writingExample,
for (final item in vocabularyBySegmentId(segment.id))
'${item.word}\n${item.example}',
],
].join('\n');
for (final id in entry.value) {
for (final id in lesson.targetItemIds) {
expect(
coreItemUsedIn(id, text, word: a0CoreItems[id]),
coreItemUsedIn(id, text),
isTrue,
reason: '${entry.key} → $id ${a0CoreItems[id]}',
reason: '${lesson.id} → $id ${a0CoreItems[id]}',
);
}
}