155 lines
4.8 KiB
Dart
155 lines
4.8 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:kouyu_english/core/generated_content.dart';
|
||
|
||
void main() {
|
||
const valid = '''{
|
||
"schemaVersion":"review-variant-1",
|
||
"variantId":"ai-a0-p12-001",
|
||
"targetItemId":"A0-P12",
|
||
"prompt":"请用英语说你来自哪里。",
|
||
"expectedAnswer":"I’m from [place]."
|
||
}''';
|
||
|
||
test('accepts a bounded variant for its requested frozen target', () {
|
||
final item = decodeGeneratedReviewVariant(
|
||
valid,
|
||
expectedTargetItemId: 'A0-P12',
|
||
);
|
||
|
||
expect(item, isNotNull);
|
||
expect(item!.targetItemId, 'A0-P12');
|
||
});
|
||
|
||
test('rejects an AI variant that changes the target identity', () {
|
||
expect(
|
||
decodeGeneratedReviewVariant(valid, expectedTargetItemId: 'A0-P01'),
|
||
isNull,
|
||
);
|
||
});
|
||
|
||
test('rejects unknown schema fields', () {
|
||
final invalid = valid.replaceFirst('}', ',"unsafe":"ignore"}');
|
||
expect(
|
||
decodeGeneratedReviewVariant(invalid, expectedTargetItemId: 'A0-P12'),
|
||
isNull,
|
||
);
|
||
});
|
||
|
||
const writing = '''{
|
||
"schemaVersion":"writing-feedback-1",
|
||
"lessonId":"a0-06",
|
||
"verdict":"rewrite",
|
||
"feedback":"加上 from 就能说清来自哪里。",
|
||
"suggestion":"I'm from Hong Kong.",
|
||
"missing":["来自哪里"]
|
||
}''';
|
||
|
||
test('accepts bounded writing feedback for its lesson', () {
|
||
final feedback = decodeWritingAiFeedback(
|
||
writing,
|
||
expectedLessonId: 'a0-06',
|
||
);
|
||
|
||
expect(feedback, isNotNull);
|
||
expect(feedback!.verdict, 'rewrite');
|
||
});
|
||
|
||
test('rejects writing feedback for a different lesson', () {
|
||
expect(decodeWritingAiFeedback(writing, expectedLessonId: 'a0-07'), isNull);
|
||
});
|
||
|
||
const lesson = '''{
|
||
"schemaVersion":"lesson-1",
|
||
"lessonId":"ai-a0-p12-1",
|
||
"revision":1,
|
||
"stageVersion":"A0-1.0",
|
||
"source":"aiGenerated",
|
||
"status":"validated",
|
||
"abilityIds":["A0-C06"],
|
||
"prerequisiteIds":["a0-05"],
|
||
"targetItemIds":["A0-P12"],
|
||
"receptiveChunks":[],
|
||
"newItemIds":[],
|
||
"previewItemIds":[],
|
||
"estimatedMinutes":10,
|
||
"tasks":[
|
||
{"taskId":"l","skill":"listening","type":"listenChoice","prompt":"听后选择。","stimulus":"I am from Hong Kong.","answer":"Hong Kong","targetItemIds":["A0-P12"]},
|
||
{"taskId":"s","skill":"speaking","type":"repeat","prompt":"跟读。","stimulus":"I am from Hong Kong.","answer":"I am from Hong Kong.","targetItemIds":["A0-P12"]},
|
||
{"taskId":"r","skill":"reading","type":"readAnswer","prompt":"读后回答。","stimulus":"I am from Hong Kong.","answer":"Hong Kong","targetItemIds":["A0-P12"]},
|
||
{"taskId":"w","skill":"writing","type":"writeAnswer","prompt":"写一句。","stimulus":"来自香港。","answer":"I am from Hong Kong.","targetItemIds":["A0-P12"]}
|
||
]
|
||
}''';
|
||
|
||
test('accepts a bounded four-skill adaptive lesson', () {
|
||
final decoded = decodeGeneratedLesson(
|
||
lesson,
|
||
expectedTargetItemId: 'A0-P12',
|
||
);
|
||
|
||
expect(decoded, isNotNull);
|
||
expect(decoded!.tasks.map((task) => task.skill).toSet(), hasLength(4));
|
||
});
|
||
|
||
test('adaptive answer check accepts harmless punctuation variation', () {
|
||
final task = decodeGeneratedLesson(
|
||
lesson,
|
||
expectedTargetItemId: 'A0-P12',
|
||
)!.tasks[3];
|
||
|
||
expect(matchesAdaptiveLessonAnswer(task, "i am from hong kong!"), isTrue);
|
||
});
|
||
|
||
test('adaptive answer check rejects a missing target token', () {
|
||
final task = decodeGeneratedLesson(
|
||
lesson,
|
||
expectedTargetItemId: 'A0-P12',
|
||
)!.tasks[3];
|
||
|
||
expect(matchesAdaptiveLessonAnswer(task, 'I am from Hong.'), isFalse);
|
||
});
|
||
|
||
test('adaptive answer spec accepts listed slots and rejects exclusions', () {
|
||
const task = GeneratedLessonTask(
|
||
taskId: 'speaking',
|
||
skill: 'speaking',
|
||
type: 'repeat',
|
||
prompt: '说一句。',
|
||
stimulus: 'I am from Hong Kong.',
|
||
answer: 'I am from Hong Kong.',
|
||
targetItemIds: ['A0-P12'],
|
||
answerSpec: GeneratedAnswerSpec(
|
||
requiredAnyPhrases: [
|
||
['I am', "I'm"],
|
||
['from'],
|
||
['Hong Kong'],
|
||
],
|
||
acceptedAnswers: ['I am from Hong Kong.', "I'm from Hong Kong."],
|
||
forbiddenPhrases: ['Beijing'],
|
||
),
|
||
);
|
||
|
||
expect(matchesAdaptiveLessonAnswer(task, "I'm from Hong Kong!"), isTrue);
|
||
expect(matchesAdaptiveLessonAnswer(task, 'I am from Beijing.'), isFalse);
|
||
expect(matchesAdaptiveLessonAnswer(task, 'Hong Kong.'), isFalse);
|
||
});
|
||
|
||
test('rejects an adaptive lesson with a different frozen target', () {
|
||
expect(
|
||
decodeGeneratedLesson(lesson, expectedTargetItemId: 'A0-P11'),
|
||
isNull,
|
||
);
|
||
});
|
||
|
||
test(
|
||
'rejects an adaptive lesson that introduces an unknown English word',
|
||
() {
|
||
final invalid = lesson.replaceFirst('Hong Kong', 'library');
|
||
|
||
expect(
|
||
decodeGeneratedLesson(invalid, expectedTargetItemId: 'A0-P12'),
|
||
isNull,
|
||
);
|
||
},
|
||
);
|
||
}
|