Some checks are pending
CI / validate (push) Waiting to run
Phase 2 des cards→wordeck Big-Bang-Rebrand:
- 4 package.json: @cards/* → @wordeck/*
- packages/cards-domain/ → packages/wordeck-domain/
- 41+12 Files: from '@cards/domain' → '@wordeck/domain'
- pgSchema('cards') → pgSchema('wordeck') (Drizzle-Schema)
- 17 Files: process.env.CARDS_* → process.env.WORDECK_*
- docker-compose Service-Names: cards-* → wordeck-*
- docker-compose Volume: /Volumes/ManaData/cards → wordeck
- env-vars in compose: CARDS_DB_PASSWORD/_API_VERSION/_DSGVO_SERVICE_KEY etc. → WORDECK_*
- Log-Prefixes + Error-Strings + manifest-id 'cards' → 'wordeck'
- CORS-Origin cardecky.mana.how → wordeck.com
- .env.production.example umbenannt + S3-Key entfernt (kein MinIO mehr)
Type-Check 0 Errors in api+domain+web, 51/51 Domain-Tests grün.
DB-Rename + Container/Volume-Rename auf mana-server folgen in nächstem
Commit nach Verzeichnis-Rename.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
2.8 KiB
TypeScript
100 lines
2.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
|
|
import {
|
|
buildScheduler,
|
|
fromFsrsCard,
|
|
gradeReview,
|
|
newReview,
|
|
subIndexCount,
|
|
toFsrsCard,
|
|
} from '../src/fsrs.ts';
|
|
|
|
describe('newReview', () => {
|
|
it('initializes with state=new and reps=0', () => {
|
|
const r = newReview({
|
|
userId: 'u-1',
|
|
cardId: 'c-1',
|
|
now: new Date('2026-05-08T10:00:00Z'),
|
|
});
|
|
expect(r.card_id).toBe('c-1');
|
|
expect(r.sub_index).toBe(0);
|
|
expect(r.user_id).toBe('u-1');
|
|
expect(r.state).toBe('new');
|
|
expect(r.reps).toBe(0);
|
|
expect(r.lapses).toBe(0);
|
|
expect(r.due).toBe('2026-05-08T10:00:00.000Z');
|
|
});
|
|
|
|
it('honours provided sub_index', () => {
|
|
const r = newReview({ userId: 'u-1', cardId: 'c-1', subIndex: 2 });
|
|
expect(r.sub_index).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe('gradeReview', () => {
|
|
const fixedNow = new Date('2026-05-08T10:00:00Z');
|
|
const reviewedAt = new Date('2026-05-08T10:01:00Z');
|
|
const baseReview = newReview({
|
|
userId: 'u-1',
|
|
cardId: 'c-1',
|
|
now: fixedNow,
|
|
});
|
|
|
|
it('Again from new keeps reps=0 increments lapses', () => {
|
|
// Disable fuzz for deterministic test outputs
|
|
const next = gradeReview(baseReview, 'again', reviewedAt, { enable_fuzz: false });
|
|
expect(next.state).not.toBe('new');
|
|
expect(next.due).not.toBe(baseReview.due);
|
|
expect(next.reps).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
it('Easy from new transitions to a future-dated review', () => {
|
|
const next = gradeReview(baseReview, 'easy', reviewedAt, { enable_fuzz: false });
|
|
expect(new Date(next.due).getTime()).toBeGreaterThan(reviewedAt.getTime());
|
|
});
|
|
|
|
it('preserves card_id, sub_index, user_id', () => {
|
|
const next = gradeReview(baseReview, 'good', reviewedAt, { enable_fuzz: false });
|
|
expect(next.card_id).toBe(baseReview.card_id);
|
|
expect(next.sub_index).toBe(baseReview.sub_index);
|
|
expect(next.user_id).toBe(baseReview.user_id);
|
|
});
|
|
});
|
|
|
|
describe('toFsrsCard / fromFsrsCard roundtrip', () => {
|
|
it('roundtrips a new review without loss', () => {
|
|
const r = newReview({ userId: 'u-1', cardId: 'c-1' });
|
|
const fc = toFsrsCard(r);
|
|
const back = fromFsrsCard(r, fc);
|
|
expect(back.due).toBe(r.due);
|
|
expect(back.stability).toBe(r.stability);
|
|
expect(back.state).toBe(r.state);
|
|
});
|
|
});
|
|
|
|
describe('subIndexCount', () => {
|
|
it('basic = 1, basic-reverse = 2', () => {
|
|
expect(subIndexCount('basic')).toBe(1);
|
|
expect(subIndexCount('basic-reverse')).toBe(2);
|
|
});
|
|
|
|
it('unknown type defaults to 1', () => {
|
|
expect(subIndexCount('unknown-future-type')).toBe(1);
|
|
});
|
|
|
|
it('cloze wirft — Caller muss subIndexCountForCloze nutzen', () => {
|
|
expect(() => subIndexCount('cloze')).toThrow(/subIndexCountForCloze/);
|
|
});
|
|
});
|
|
|
|
describe('buildScheduler', () => {
|
|
it('builds with defaults', () => {
|
|
const s = buildScheduler();
|
|
expect(s).toBeDefined();
|
|
});
|
|
|
|
it('honours per-deck overrides', () => {
|
|
const s = buildScheduler({ request_retention: 0.85, enable_fuzz: false });
|
|
expect(s).toBeDefined();
|
|
});
|
|
});
|