import { describe, it, expect } from 'vitest'; import { initialSrs, review } from './srs'; const MS_PER_DAY = 86_400_000; const NOW = 1_700_000_000_000; describe('initialSrs', () => { it('creates a neutral, immediately-due card', () => { const s = initialSrs(NOW); expect(s).toEqual({ ease: 2.5, intervalDays: 0, reps: 0, lapses: 0, due: NOW, }); }); }); describe('review', () => { it('does not mutate the input state', () => { const s = initialSrs(NOW); const frozen = { ...s }; review(s, 2, NOW); expect(s).toEqual(frozen); }); it('produces monotonically increasing intervals on repeated "good"', () => { let s = initialSrs(NOW); const intervals: number[] = []; let t = NOW; for (let i = 0; i < 5; i++) { s = review(s, 2, t); intervals.push(s.intervalDays); t += s.intervalDays * MS_PER_DAY; } // 1 day, 6 days, then *ease each time -> strictly increasing. for (let i = 1; i < intervals.length; i++) { expect(intervals[i]!).toBeGreaterThan(intervals[i - 1]!); } expect(intervals[0]).toBe(1); expect(intervals[1]).toBe(6); }); it('increments reps on passing grades and resets on "again"', () => { let s = initialSrs(NOW); s = review(s, 2, NOW); // good s = review(s, 2, NOW); // good expect(s.reps).toBe(2); s = review(s, 0, NOW); // again expect(s.reps).toBe(0); expect(s.lapses).toBe(1); }); it('floors ease at 1.3 even after many "again" reviews', () => { let s = initialSrs(NOW); for (let i = 0; i < 20; i++) s = review(s, 0, NOW); expect(s.ease).toBe(1.3); }); it('lowers ease on hard, raises it on easy', () => { const good = review(initialSrs(NOW), 2, NOW); expect(good.ease).toBe(2.5); const hard = review(initialSrs(NOW), 1, NOW); expect(hard.ease).toBeCloseTo(2.35, 5); const easy = review(initialSrs(NOW), 3, NOW); expect(easy.ease).toBeCloseTo(2.65, 5); }); it('computes due = now + intervalDays * 86400000 and sets lastReviewed', () => { const s = review(initialSrs(NOW), 2, NOW); expect(s.intervalDays).toBe(1); expect(s.due).toBe(NOW + 1 * MS_PER_DAY); expect(s.lastReviewed).toBe(NOW); }); it('gives "easy" a longer interval than "good" at the same step', () => { const good = review(initialSrs(NOW), 2, NOW); const easy = review(initialSrs(NOW), 3, NOW); expect(easy.intervalDays).toBeGreaterThan(good.intervalDays); }); it('keeps "hard" shorter than "good" for a reviewed card (reps >= 2)', () => { // Bring a card to reps=2 with two Goods (interval 6, ease 2.5). let s = initialSrs(NOW); s = review(s, 2, NOW); s = review(s, 2, NOW); expect(s.reps).toBe(2); const hard = review(s, 1, NOW); const good = review(s, 2, NOW); expect(hard.intervalDays).toBeLessThan(good.intervalDays); // And Hard still grows the interval (it's a pass, not a lapse). expect(hard.intervalDays).toBeGreaterThan(s.intervalDays); }); });