import 'package:flutter_test/flutter_test.dart'; import 'package:kouyu_english/core/courses/courses.dart'; /// The free "初次见面" scene, which is always open. LessonDialogue get meet => sceneById('a0-meet').script; void main() { group('对话任务校验', () { test('每个对话都为每一轮声明了任务标签', () { for (final entry in segmentDialogues.entries) { final script = entry.value; expect( script.taskLabels.length, script.prompts.length, reason: '${entry.key} 的任务标签数量要和回合数一致', ); expect( script.requiredTerms.length, script.prompts.length, reason: '${entry.key} 的校验词组数量要和回合数一致', ); for (final group in script.requiredTerms) { expect(group, isNotEmpty); } } expect(meet.taskLabels.length, meet.prompts.length); expect(meet.requiredTerms.length, meet.prompts.length); }); test('示范答案能通过对应回合的校验', () { for (final entry in {...segmentDialogues, 'scene': meet}.entries) { final script = entry.value; for (var stage = 0; stage < script.hints.length; stage++) { expect( matchesDialogueStage(script, stage, script.hints[stage]), isTrue, reason: '${entry.key} 第 ${stage + 1} 轮的示范答案应当通过', ); } } }); test('跑题回答不再因为关键词沾边而通过', () { // 旧实现用 text.contains('it'),下面这些句子全部会被判为完成任务。 expect( matchesDialogueStage(segmentDialogues['a0-05-a']!, 0, 'I did it.'), isFalse, ); expect( matchesDialogueStage(segmentDialogues['a0-08-c']!, 1, 'It is good.'), isFalse, ); expect( matchesDialogueStage(segmentDialogues['a0-09-a']!, 2, 'I like tea.'), isFalse, ); expect(matchesDialogueStage(meet, 0, 'Hello.'), isFalse); expect(matchesDialogueStage(meet, 1, 'I am good.'), isFalse); }); test('自由场景接受同样正确的其他说法', () { // 旧正则只认 good/okay/tired,也不认 my name's。 expect(matchesDialogueStage(meet, 0, "My name's Shen."), isTrue); expect(matchesDialogueStage(meet, 0, 'I am Shen.'), isTrue); expect(matchesDialogueStage(meet, 2, "I'm fine, thanks."), isTrue); expect(matchesDialogueStage(meet, 2, 'I am great!'), isTrue); expect(matchesDialogueStage(meet, 3, 'Where are you from?'), isTrue); expect(matchesDialogueStage(meet, 3, 'Do you like tea?'), isTrue); }); test('只写半句不算完成任务', () { expect(matchesDialogueStage(meet, 0, "I'm"), isFalse); expect( matchesDialogueStage(segmentDialogues['a0-04-c']!, 0, 'My number is'), isFalse, ); expect( matchesDialogueStage( segmentDialogues['a0-04-c']!, 0, 'My number is one-three-eight.', ), isTrue, ); }); test('拼读和数字这类结构化要求可以识别', () { expect( matchesDialogueStage(segmentDialogues['a0-02-a']!, 1, 'S-H-E-N'), isTrue, ); }); test('拼读忽略大小写和连字符', () { final spelling = segmentDialogues['a0-02-a']!; for (final answer in [ 'A-A-A', 'aaa', 'AAA', 'a-a-a', 'A - A - A', 'a a a', 'Shen', 'S-H-E-N.', 'She-n', 'L-I', 'Li', 'My name is Alex. A-L-E-X.', ]) { expect( matchesDialogueStage(spelling, 1, answer), isTrue, reason: answer, ); } for (final answer in ['a', 'My name is Alex.', '123', '---']) { expect( matchesDialogueStage(spelling, 1, answer), isFalse, reason: answer, ); } expect( matchesDialogueStage( segmentDialogues['a0-08-c']!, 1, "It's three o'clock.", ), isTrue, ); expect( matchesDialogueStage(segmentDialogues['a0-08-c']!, 1, "It's Monday."), isFalse, ); }); test('任务标签可用于提示和总结', () { expect(dialogueTaskLabel(meet, 0), '介绍姓名'); expect(dialogueTaskLabel(meet, 9), '完成本轮任务'); }); test('输入校验忽略多余空格与大小写', () { expect( matchesDialogueStage(meet, 0, " MY NAME IS SHEN "), isTrue, ); expect( matchesDialogueStage( segmentDialogues['a0-02-a']!, 1, " S H E N ", ), isTrue, ); expect( matchesDialogueStage( segmentDialogues['a0-04-c']!, 0, "MY NUMBER IS ONE-THREE-EIGHT", ), isTrue, ); expect( matchesDialogueStage( segmentDialogues['a0-08-c']!, 1, "IT'S THREE O'CLOCK", ), isTrue, ); }); test('给 AI 的可用词表随课程递增且不越界', () { final first = taughtLanguageUpTo('a0-01'); final later = taughtLanguageUpTo('a0-09'); expect(first, isNotEmpty); expect(later.length, greaterThan(first.length)); expect(first.every(later.contains), isTrue); expect(later.length, lessThanOrEqualTo(a0TaughtLanguage.length)); expect( first.any((word) => word.toLowerCase().contains('coffee')), isFalse, ); }); }); }