课程内容 - 重写全部 21 个 A1 单元的听读材料,把本单元理解词织进听/读文本, 单元内理解词复现率从约 20% 提升到约 77%(各单元 56–96%)。 - 修正 U01 房间号与机场大巴同为 thirty 的撞车(改为 17/30/40)。 - 校验器容差按级别读取(A1 为 8%),materials 覆盖率、字数、 选项子串等校验全部通过;course_content_test 通过。 黑夜模式 - app_theme 拆分明/暗两套调色板,AppColors 随亮度切换; main 用 theme/darkTheme/themeMode + builder 镜像已解析亮度; 主题偏好持久化到快照;进度页新增“外观主题”切换。 文档 - COURSE-PACK-JSON.md 更新 A1 词池覆盖(840/933)与材料复现约定。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
135 lines
3.8 KiB
Dart
135 lines
3.8 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:kouyu_english/core/a0_core.dart';
|
||
import 'package:kouyu_english/core/models.dart';
|
||
import 'package:kouyu_english/core/review_feedback.dart';
|
||
|
||
ReviewItem review(String id, String target) => ReviewItem(
|
||
id: id,
|
||
target: target,
|
||
prompt: 'Test prompt',
|
||
hint: target,
|
||
dueAt: DateTime(2026),
|
||
skill: '回忆表达',
|
||
);
|
||
|
||
void main() {
|
||
test('does not accept unrelated English for a core phrase', () {
|
||
final result = ReviewFeedback.check(
|
||
review('A0-P12', "I'm from [place]."),
|
||
'I like tea.',
|
||
);
|
||
expect(result.complete, isFalse);
|
||
});
|
||
|
||
test('accepts a new place in the from pattern', () {
|
||
final result = ReviewFeedback.check(
|
||
review('A0-P12', "I'm from [place]."),
|
||
"I'm from Guangzhou.",
|
||
);
|
||
expect(result.complete, isTrue);
|
||
});
|
||
|
||
test('checks an A0 word without accepting a different word', () {
|
||
expect(
|
||
ReviewFeedback.check(review('A0-W08', 'three'), 'four').complete,
|
||
isFalse,
|
||
);
|
||
expect(
|
||
ReviewFeedback.check(review('A0-W08', 'three'), 'three').complete,
|
||
isTrue,
|
||
);
|
||
});
|
||
|
||
test('A0-P17 accepts standard o clock expressions', () {
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P17', "It's [hour] o'clock."),
|
||
"It's three o'clock.",
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P17', "It's [hour] o'clock."),
|
||
'It is two o’clock.',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P17', "It's [hour] o'clock."),
|
||
'It is five oclock.',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
});
|
||
|
||
test('A0-P20 accepts please say that again and please speak slowly', () {
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P20', 'Please say that again.'),
|
||
'Please say that again.',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P20', 'Please say that again.'),
|
||
'Please speak slowly.',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
});
|
||
|
||
test('ignores multiple spaces and casing across review items', () {
|
||
// Multi-space and uppercase on A0-P03
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P03', 'Nice to meet you.'),
|
||
' NICE TO MEET YOU! ',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
// Multi-space and uppercase on A0-P08
|
||
expect(
|
||
ReviewFeedback.check(
|
||
review('A0-P08', 'My number is ...'),
|
||
'MY NUMBER IS ONE TWO THREE',
|
||
).complete,
|
||
isTrue,
|
||
);
|
||
// Multi-space on word item
|
||
expect(
|
||
ReviewFeedback.check(review('A0-W08', 'three'), ' THREE ').complete,
|
||
isTrue,
|
||
);
|
||
});
|
||
|
||
test('a dictation review needs the whole sentence that was played', () {
|
||
final item = review('A0-W24', 'water').copyWith(
|
||
skill: dictationSkill,
|
||
prompt: coreReviewVariant('A0-W24', 1).prompt,
|
||
);
|
||
expect(ReviewFeedback.check(item, 'water').complete, isFalse);
|
||
expect(ReviewFeedback.check(item, 'I like watter.').complete, isFalse);
|
||
expect(ReviewFeedback.check(item, 'i like water').complete, isTrue);
|
||
});
|
||
|
||
test('offline review variants rotate recall, dictation and speaking', () {
|
||
expect(coreReviewVariant('A0-P12', 0).skill, '回忆表达');
|
||
expect(coreReviewVariant('A0-P12', 1).skill, dictationSkill);
|
||
expect(coreReviewVariant('A0-P12', 2).skill, spokenRecallSkill);
|
||
expect(coreReviewVariant('A0-P12', 3).skill, '回忆表达');
|
||
for (final id in a0CoreItems.keys) {
|
||
final sentence = a0DictationSentences[id];
|
||
expect(sentence, isNotNull, reason: id);
|
||
// Each dictation sentence really contains the item it checks.
|
||
expect(
|
||
coreItemUsedIn(id, sentence!.sentence, word: a0CoreItems[id]),
|
||
isTrue,
|
||
reason: id,
|
||
);
|
||
}
|
||
});
|
||
}
|