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, ); }, ); }