Triviaverse / src /lib /game /engine.test.ts
Simeon Garratt
feat: game engine state machine with reducer pattern
b5ae9b2
Raw
History Blame Contribute Delete
4.03 kB
import { describe, it, expect } from "vitest";
import { gameReducer, createInitialState } from "./engine";
import type { ShuffledQuestion } from "./types";
const mockQuestion = (id: string, difficulty: "easy" | "medium" | "hard" = "medium"): ShuffledQuestion => ({
id, category: "General", difficulty, questionText: `Question ${id}?`,
correctAnswer: "Correct", incorrectAnswers: ["Wrong1", "Wrong2", "Wrong3"],
allAnswers: ["Correct", "Wrong1", "Wrong2", "Wrong3"],
});
describe("createInitialState", () => {
it("creates idle state for quick-fire", () => {
const state = createInitialState("quick-fire", [mockQuestion("1")]);
expect(state.phase).toBe("idle");
expect(state.mode).toBe("quick-fire");
expect(state.score).toBe(0);
expect(state.currentQuestionIndex).toBe(0);
});
});
describe("gameReducer", () => {
it("START transitions from idle to playing", () => {
const state = createInitialState("quick-fire", [mockQuestion("1")]);
const next = gameReducer(state, { type: "START" });
expect(next.phase).toBe("playing");
expect(next.startedAt).not.toBeNull();
});
it("ANSWER_CORRECT increments score and streak", () => {
let state = createInitialState("quick-fire", [mockQuestion("1"), mockQuestion("2")]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 200 });
expect(state.score).toBe(200);
expect(state.streak).toBe(1);
expect(state.questionsCorrect).toBe(1);
expect(state.currentQuestionIndex).toBe(1);
});
it("ANSWER_WRONG resets streak to 0", () => {
let state = createInitialState("quick-fire", [mockQuestion("1"), mockQuestion("2")]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 100 });
state = gameReducer(state, { type: "ANSWER_WRONG" });
expect(state.streak).toBe(0);
expect(state.questionsCorrect).toBe(1);
expect(state.currentQuestionIndex).toBe(2);
});
it("ANSWER_WRONG in streak mode ends the game", () => {
let state = createInitialState("streak", [mockQuestion("1"), mockQuestion("2")]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "ANSWER_WRONG" });
expect(state.phase).toBe("finished");
});
it("answering last question finishes the game", () => {
let state = createInitialState("quick-fire", [mockQuestion("1")]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 100 });
expect(state.phase).toBe("finished");
});
it("TIME_UP finishes the game", () => {
let state = createInitialState("quick-fire", [mockQuestion("1")]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "TIME_UP" });
expect(state.phase).toBe("finished");
});
it("tracks streakMax across the game", () => {
let state = createInitialState("quick-fire", [
mockQuestion("1"), mockQuestion("2"), mockQuestion("3"), mockQuestion("4"),
]);
state = gameReducer(state, { type: "START" });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 100 });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 100 });
state = gameReducer(state, { type: "ANSWER_WRONG" });
state = gameReducer(state, { type: "ANSWER_CORRECT", points: 100 });
expect(state.streakMax).toBe(2);
expect(state.streak).toBe(1);
});
it("NEXT_PHASE transitions challenge-me phases", () => {
let state = createInitialState("challenge-me", [mockQuestion("1")]);
state = gameReducer(state, { type: "START" });
expect(state.challengePhase).toBe("easy");
state = gameReducer(state, { type: "NEXT_PHASE" });
expect(state.challengePhase).toBe("medium");
state = gameReducer(state, { type: "NEXT_PHASE" });
expect(state.challengePhase).toBe("hard");
state = gameReducer(state, { type: "NEXT_PHASE" });
expect(state.phase).toBe("finished");
});
});