feat: add vocabulary review planning

This commit is contained in:
Codex
2026-06-28 23:55:01 +08:00
parent dfb383d281
commit fbcfa5127e
8 changed files with 406 additions and 7 deletions

View File

@@ -876,12 +876,33 @@ async function testVocabulary() {
const stats = await request('/api/learning/vocabulary/stats', { query: { unitId: ids.vocabularyUnit } });
assert.ok(stats.item?.totalWords >= 1, 'word stats should count smoke word');
const reviewUnknown = await request('/api/learning/vocabulary/review', {
method: 'POST',
body: { userId: USER_ID, wordId: ids.vocabularyWord, result: 'unknown' },
});
assert.equal(reviewUnknown.item?.lastResult, 'unknown', 'word review should persist unknown result');
assert.equal(reviewUnknown.item?.dueLevel, 'again', 'unknown word should be scheduled again');
const reviewPlan = await request('/api/learning/vocabulary/review-plan', {
query: { unitId: ids.vocabularyUnit, reviewLimit: 10, newLimit: 10 },
});
assert.ok(Array.isArray(reviewPlan.item?.words), 'word review plan should return planned words');
assert.ok(reviewPlan.item?.words?.some(item => item.wordId === ids.vocabularyWord), 'word review plan should include due smoke word');
const progress = await request('/api/learning/vocabulary/progress', {
method: 'POST',
body: { userId: USER_ID, wordId: ids.vocabularyWord, status: 'mastered', correctDelta: 1 },
});
assert.equal(progress.item?.status, 'mastered', 'word progress should update to mastered');
const reviewKnown = await request('/api/learning/vocabulary/review', {
method: 'POST',
body: { userId: USER_ID, wordId: ids.vocabularyWord, result: 'known' },
});
assert.equal(reviewKnown.item?.lastResult, 'known', 'word review should persist known result');
assert.ok(reviewKnown.item?.reviewCount >= 1, 'word review should increment review count');
assert.ok(reviewKnown.item?.nextReviewDate, 'word review should compute next review date');
const favorite = await request('/api/learning/vocabulary/favorites', {
method: 'POST',
body: { userId: USER_ID, wordId: ids.vocabularyWord, favorite: true },