feat: 完善听力口语辅助状态记录、练习分块与复习抽查间隔机制
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user