import { describe, it, expect } from "vitest"; import { generateLetters, createExerciseState, handleKeyPress, getCurrentLetter, isExerciseComplete, } from "./typing"; import { getKeysUpToLevel } from "./levels"; describe("generateLetters", () => { it("returns 8-12 letters for level 1", () => { for (let i = 0; i < 20; i++) { const letters = generateLetters(1); expect(letters.length).toBeGreaterThanOrEqual(8); expect(letters.length).toBeLessThanOrEqual(12); } }); it("only uses keys from level 1 key set (f, j, space)", () => { const validKeys = ["f", "j", " "]; for (let i = 0; i < 20; i++) { const letters = generateLetters(1); for (const letter of letters) { expect(validKeys).toContain(letter); } } }); it("only uses keys from levels 1-3 key set for level 3", () => { const validKeys = getKeysUpToLevel(3); for (let i = 0; i < 20; i++) { const letters = generateLetters(3); for (const letter of letters) { expect(validKeys).toContain(letter); } } }); }); describe("createExerciseState", () => { it("sets initial interval to 3000ms", () => { const state = createExerciseState(1); expect(state.intervalMs).toBe(3000); }); it("starts at letter index 0 with 0 correct, 0 errors, 0 streak", () => { const state = createExerciseState(1); expect(state.currentLetterIndex).toBe(0); expect(state.totalCorrect).toBe(0); expect(state.totalErrors).toBe(0); expect(state.correctStreak).toBe(0); }); }); describe("handleKeyPress", () => { it("increments currentLetterIndex and totalCorrect on correct key", () => { const state = createExerciseState(1); const expected = state.letters[0]!; const result = handleKeyPress(state, expected); expect(result.correct).toBe(true); expect(state.currentLetterIndex).toBe(1); expect(state.totalCorrect).toBe(1); }); it("does NOT increment currentLetterIndex on wrong key, increments totalErrors", () => { const state = createExerciseState(1); // Pick a key that is definitely wrong const expected = state.letters[0]!; const wrongKey = expected === "f" ? "j" : "f"; const result = handleKeyPress(state, wrongKey); expect(result.correct).toBe(false); expect(state.currentLetterIndex).toBe(0); expect(state.totalErrors).toBe(1); }); it("increments correctStreak on correct key", () => { const state = createExerciseState(1); const expected = state.letters[0]!; handleKeyPress(state, expected); expect(state.correctStreak).toBe(1); }); it("resets correctStreak to 0 on error", () => { const state = createExerciseState(1); // Get one correct to build streak handleKeyPress(state, state.letters[0]!); expect(state.correctStreak).toBe(1); // Now wrong key const expected = state.letters[1]!; const wrongKey = expected === "f" ? "j" : "f"; handleKeyPress(state, wrongKey); expect(state.correctStreak).toBe(0); }); it("decreases interval by 200ms after 2 correct in sequence", () => { const state = createExerciseState(1); expect(state.intervalMs).toBe(3000); handleKeyPress(state, state.letters[0]!); handleKeyPress(state, state.letters[1]!); expect(state.intervalMs).toBe(2800); }); it("increases interval by 500ms after 1 error", () => { const state = createExerciseState(1); // Force an error const expected = state.letters[0]!; const wrongKey = expected === "f" ? "j" : "f"; handleKeyPress(state, wrongKey); expect(state.intervalMs).toBe(3500); }); it("interval never goes below 1500ms", () => { const state = createExerciseState(1); // Force interval down to minimum state.intervalMs = 1600; state.correctStreak = 1; handleKeyPress(state, state.letters[0]!); // After 2 correct streak, -200ms: 1600 -> 1500 (clamped) expect(state.intervalMs).toBe(1500); // Try again: force streak state.correctStreak = 1; handleKeyPress(state, state.letters[1]!); // Should stay at 1500 expect(state.intervalMs).toBe(1500); }); it("interval never goes above 4000ms", () => { const state = createExerciseState(1); state.intervalMs = 3800; const expected = state.letters[0]!; const wrongKey = expected === "f" ? "j" : "f"; handleKeyPress(state, wrongKey); // 3800 + 500 = 4300, clamped to 4000 expect(state.intervalMs).toBe(4000); }); it("exercise is complete when currentLetterIndex equals letters.length", () => { const state = createExerciseState(1); // Type all letters correctly let lastResult = { correct: false, exerciseComplete: false, newInterval: 0 }; for (let i = 0; i < state.letters.length; i++) { lastResult = handleKeyPress(state, state.letters[i]!); } expect(lastResult.exerciseComplete).toBe(true); expect(isExerciseComplete(state)).toBe(true); }); }); describe("getCurrentLetter", () => { it("returns the current letter", () => { const state = createExerciseState(1); expect(getCurrentLetter(state)).toBe(state.letters[0]); }); it("returns null when exercise is complete", () => { const state = createExerciseState(1); state.currentLetterIndex = state.letters.length; expect(getCurrentLetter(state)).toBeNull(); }); }); describe("isExerciseComplete", () => { it("returns false when exercise is in progress", () => { const state = createExerciseState(1); expect(isExerciseComplete(state)).toBe(false); }); it("returns true when all letters are typed", () => { const state = createExerciseState(1); state.currentLetterIndex = state.letters.length; expect(isExerciseComplete(state)).toBe(true); }); });