Files
English/kouyu_english/test/dialogue_task_test.dart
T
shenleiandClaude Opus 5 7e29c5449f feat: 重做阅读找答案题与 AI 情境对话,并归拢本地识别、查词等既有改动
阅读"在对话里找到答案":
- 16 道题全部重写,干扰项真实出现在对话里,靠说话人归属或否定句才能作答
- 选项按题目内容确定性打乱,答案不再固定排第一;听力环节同样处理
- 去掉超纲干扰项、重复题干,收紧自由作答匹配(过去单个字母也能判对)

AI 情境对话:
- 提示词区分"AI 这一句要做什么"与"学习者随后要完成什么",并下发已教词句清单
- JSON 只强制 reply,translation/feedback 可选;不再索要用不上的 slots/evidence
- AI 不可用时页面明确提示当前回复来自内置示范脚本
- 删掉按 stage 下标猜中文翻译的兜底,避免译文与英文对不上
- 整课对话改用逐轮必需表达校验,替换"关键词沾边就算过";修正自由场景正则误伤
- 总结的"完成任务"按实际通过的轮次生成;模型点评只在结束页呈现一次
- 自由场景支持草稿续练(独立存储槽);修正回答轮数文案与永不解锁的场景标注

同时提交此前工作区中累积的改动:SenseVoice 本地识别、查词/句型解析卡、
复习与测评页调整等,并补充对话校验、选项分布和句子解析的测试。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:38:23 +09:00

98 lines
4.2 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:kouyu_english/core/seed_courses.dart';
void main() {
group('对话任务校验', () {
test('每个对话都为每一轮声明了任务标签', () {
for (final entry in a0Dialogues.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);
}
}
for (final entry in a0SegmentDialogues.entries) {
expect(
entry.value.taskLabels.length,
entry.value.prompts.length,
reason: '${entry.key} 的任务标签数量要和回合数一致',
);
}
expect(a0MeetDialogue.taskLabels.length, a0MeetDialogue.prompts.length);
expect(a0MeetDialogue.requiredTerms.length, a0MeetDialogue.prompts.length);
});
test('示范答案能通过对应回合的校验', () {
for (final entry in {...a0Dialogues, 'scene': a0MeetDialogue}.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(a0Dialogues['a0-05']!, 0, 'I did it.'), isFalse);
expect(matchesDialogueStage(a0Dialogues['a0-08']!, 0, 'It is good.'), isFalse);
expect(matchesDialogueStage(a0Dialogues['a0-09']!, 2, 'I like tea.'), isFalse);
expect(matchesDialogueStage(a0MeetDialogue, 0, 'Hello.'), isFalse);
expect(matchesDialogueStage(a0MeetDialogue, 1, 'I am good.'), isFalse);
});
test('自由场景接受同样正确的其他说法', () {
// 旧正则只认 good/okay/tired,也不认 my name's。
expect(matchesDialogueStage(a0MeetDialogue, 0, "My name's Shen."), isTrue);
expect(matchesDialogueStage(a0MeetDialogue, 0, 'I am Shen.'), isTrue);
expect(matchesDialogueStage(a0MeetDialogue, 2, "I'm fine, thanks."), isTrue);
expect(matchesDialogueStage(a0MeetDialogue, 2, 'I am great!'), isTrue);
expect(matchesDialogueStage(a0MeetDialogue, 3, 'Where are you from?'), isTrue);
expect(matchesDialogueStage(a0MeetDialogue, 3, 'Do you like tea?'), isTrue);
});
test('只写半句不算完成任务', () {
expect(matchesDialogueStage(a0MeetDialogue, 0, "I'm"), isFalse);
expect(matchesDialogueStage(a0Dialogues['a0-04']!, 0, 'My number is'), isFalse);
expect(
matchesDialogueStage(a0Dialogues['a0-04']!, 0, 'My number is one-three-eight.'),
isTrue,
);
});
test('拼读和数字这类结构化要求可以识别', () {
expect(matchesDialogueStage(a0Dialogues['a0-02']!, 1, 'S-H-E-N'), isTrue);
expect(matchesDialogueStage(a0Dialogues['a0-02']!, 1, 'Shen'), isFalse);
expect(matchesDialogueStage(a0Dialogues['a0-08']!, 2, "It's three o'clock."), isTrue);
expect(matchesDialogueStage(a0Dialogues['a0-08']!, 2, "It's Monday."), isFalse);
});
test('任务标签可用于提示和总结', () {
expect(dialogueTaskLabel(a0MeetDialogue, 0), '介绍姓名');
expect(dialogueTaskLabel(a0MeetDialogue, 9), '完成本轮任务');
});
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(allTaughtLanguage.length));
expect(first.any((word) => word.toLowerCase().contains('coffee')), isFalse);
});
});
}