Files
English/kouyu_english/test/learning_support_test.dart
T

181 lines
5.7 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:kouyu_english/core/app_state.dart';
import 'package:kouyu_english/core/models.dart';
void main() {
test(
'listening help survives resume and cannot earn independent evidence',
() {
final first = AppState()..lessonStep = LessonStep.listening;
first.recordListeningPlayback(slow: true);
first.revealListeningTranscript();
first.recordListeningMiss();
final resumed = AppState()..restoreForTest(first.snapshotForTest());
expect(resumed.listeningAudioPlayed, isTrue);
expect(resumed.listeningSupport, ListeningSupport.transcript);
expect(resumed.listeningAttempts, 1);
resumed.completeListening(recognized: true);
final evidence = resumed.attemptEvidence
.where((item) => item.taskId.endsWith('listening-check'))
.toList();
expect(evidence, isNotEmpty);
expect(
evidence.every((item) => item.outcome == EvidenceKind.assisted),
isTrue,
);
expect(evidence.first.listeningSupport, ListeningSupport.transcript);
expect(evidence.first.firstTry, isFalse);
expect(evidence.first.attemptCount, 2);
},
);
test('unaided first listening answer earns recognition evidence', () {
final state = AppState()..lessonStep = LessonStep.listening;
state.recordListeningPlayback();
state.completeListening(recognized: true);
final evidence = state.attemptEvidence
.where((item) => item.taskId.endsWith('listening-check'))
.toList();
expect(evidence, isNotEmpty);
expect(
evidence.every((item) => item.outcome == EvidenceKind.independentSuccess),
isTrue,
);
expect(evidence.first.firstTry, isTrue);
});
test('a choice without playable audio cannot earn listening recognition', () {
final state = AppState()..lessonStep = LessonStep.listening;
state.completeListening(recognized: true);
final evidence = state.attemptEvidence
.where((item) => item.taskId.endsWith('listening-check'))
.toList();
expect(
evidence.every((item) => item.outcome == EvidenceKind.assisted),
isTrue,
);
});
test(
'TTS failure survives resume and gives only assisted listening credit',
() {
final state = AppState()..lessonStep = LessonStep.listening;
state.recordListeningAudioUnavailable();
final resumed = AppState()..restoreForTest(state.snapshotForTest());
expect(resumed.listeningAudioUnavailable, isTrue);
expect(resumed.listeningSupport, ListeningSupport.transcript);
resumed.completeListening(recognized: true);
expect(resumed.lessonStep, LessonStep.speaking);
expect(resumed.listeningAudioUnavailable, isFalse);
expect(
resumed.attemptEvidence
.where((entry) => entry.taskId.endsWith('listening-check'))
.every((entry) => entry.outcome == EvidenceKind.assisted),
isTrue,
);
},
);
test('skipping follow-reading does not create speaking evidence', () {
final state = AppState()..lessonStep = LessonStep.speaking;
state.completeSpeaking();
expect(state.lessonStep, LessonStep.reading);
expect(
state.attemptEvidence.where(
(entry) => entry.taskId.endsWith('-speaking'),
),
isEmpty,
);
});
test('the fourth checkpoint keeps its 30-day audit interval', () {
final state = AppState();
state.reviewQueue.add(
ReviewItem(
id: 'A0-P02',
target: "What's your name?",
prompt: '问名字',
hint: "What's your name?",
dueAt: DateTime.now().subtract(const Duration(days: 1)),
skill: '回忆表达',
successfulReviews: 3,
),
);
state.completeReview(
state.reviewQueue.single,
assisted: false,
firstTry: true,
attemptCount: 1,
rawAnswer: "What's your name?",
);
final next = state.reviewQueue.single;
expect(next.successfulReviews, 4);
expect(
next.dueAt.difference(DateTime.now()).inDays,
inInclusiveRange(29, 30),
);
expect(state.attemptEvidence.last.firstTry, isTrue);
expect(state.attemptEvidence.last.attemptCount, 1);
});
test('later clean mastery audits lengthen to 45 days', () {
final state = AppState();
state.reviewQueue.add(
ReviewItem(
id: 'A0-P02',
target: "What's your name?",
prompt: '问名字',
hint: "What's your name?",
dueAt: DateTime.now().subtract(const Duration(days: 1)),
skill: '回忆表达',
successfulReviews: 4,
),
);
state.completeReview(
state.reviewQueue.single,
assisted: false,
firstTry: true,
attemptCount: 1,
rawAnswer: "What's your name?",
);
expect(
state.reviewQueue.single.dueAt.difference(DateTime.now()).inDays,
inInclusiveRange(44, 45),
);
});
test(
'slow dictation cannot advance a checkpoint even if caller says independent',
() {
final state = AppState();
state.reviewQueue.add(
ReviewItem(
id: 'A0-P02',
target: "What's your name?",
prompt: '听写',
hint: "What's your name?",
dueAt: DateTime.now().subtract(const Duration(days: 1)),
skill: '听写',
),
);
state.completeReview(
state.reviewQueue.single,
assisted: false,
firstTry: true,
attemptCount: 1,
listeningSupport: ListeningSupport.slow,
rawAnswer: "What's your name?",
);
expect(state.reviewQueue.single.successfulReviews, 0);
expect(state.attemptEvidence.last.outcome, EvidenceKind.assisted);
expect(
state.attemptEvidence.last.listeningSupport,
ListeningSupport.slow,
);
},
);
}