AT Mot — a bilingual (EN/FR) daily word game native to the AT Protocol.
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 2.8 kB View raw
1import { describe, it, expect } from 'vitest'; 2import { computeNextStats, statsForLang, type StatsRecord } from '../src/engine/stats.js'; 3import type { GameResult } from '../src/engine/types.js'; 4 5function game(lang: 'en' | 'fr', puzzleNumber: number, solved: boolean, guessCount?: number): GameResult { 6 return { lang, puzzleNumber, solved, guessCount, rows: [] }; 7} 8 9const T = '2026-06-23T00:00:00.000Z'; 10 11describe('computeNextStats', () => { 12 it('creates a fresh lang bucket on first play', () => { 13 const s = computeNextStats(null, game('en', 1, true, 3), T); 14 const en = statsForLang(s, 'en')!; 15 expect(en.gamesPlayed).toBe(1); 16 expect(en.gamesWon).toBe(1); 17 expect(en.currentStreak).toBe(1); 18 expect(en.maxStreak).toBe(1); 19 expect(en.lastPuzzleNumber).toBe(1); 20 expect(en.guessDistribution).toEqual([{ guesses: 3, count: 1 }]); 21 }); 22 23 it('extends streak on consecutive wins and tracks max', () => { 24 let s: StatsRecord | null = null; 25 s = computeNextStats(s, game('en', 1, true, 4), T); 26 s = computeNextStats(s, game('en', 2, true, 3), T); 27 s = computeNextStats(s, game('en', 3, true, 2), T); 28 const en = statsForLang(s, 'en')!; 29 expect(en.currentStreak).toBe(3); 30 expect(en.maxStreak).toBe(3); 31 expect(en.gamesWon).toBe(3); 32 }); 33 34 it('resets streak to 0 on a loss, keeps max', () => { 35 let s: StatsRecord | null = null; 36 s = computeNextStats(s, game('en', 1, true, 4), T); 37 s = computeNextStats(s, game('en', 2, true, 3), T); 38 s = computeNextStats(s, game('en', 3, false), T); 39 const en = statsForLang(s, 'en')!; 40 expect(en.currentStreak).toBe(0); 41 expect(en.maxStreak).toBe(2); 42 expect(en.gamesPlayed).toBe(3); 43 expect(en.gamesWon).toBe(2); 44 }); 45 46 it('restarts streak at 1 after a gap', () => { 47 let s: StatsRecord | null = null; 48 s = computeNextStats(s, game('en', 1, true, 4), T); 49 s = computeNextStats(s, game('en', 5, true, 3), T); // gap 50 expect(statsForLang(s, 'en')!.currentStreak).toBe(1); 51 }); 52 53 it('keeps EN and FR streaks independent', () => { 54 let s: StatsRecord | null = null; 55 s = computeNextStats(s, game('en', 1, true, 4), T); 56 s = computeNextStats(s, game('fr', 1, true, 5), T); 57 s = computeNextStats(s, game('en', 2, true, 3), T); 58 expect(statsForLang(s, 'en')!.currentStreak).toBe(2); 59 expect(statsForLang(s, 'fr')!.currentStreak).toBe(1); 60 }); 61 62 it('does not disturb streak when replaying an older puzzle', () => { 63 let s: StatsRecord | null = null; 64 s = computeNextStats(s, game('en', 10, true, 4), T); 65 s = computeNextStats(s, game('en', 3, true, 2), T); // older puzzle 66 const en = statsForLang(s, 'en')!; 67 expect(en.lastPuzzleNumber).toBe(10); 68 expect(en.currentStreak).toBe(1); 69 expect(en.gamesPlayed).toBe(2); 70 expect(en.gamesWon).toBe(2); 71 }); 72});