nerdware/apps/server/src/game/modes/quiz/quiz-round.test.ts
AyoubBenziza 5cc9113b8c feat(quiz): quiz game mode end-to-end (server mode + playable client UI)
Server:
- quiz mode (GameRound): in-memory FR question bank (mcq + truefalse),
  per-room anti-repeat, first-vote lock, score = base + speed bonus ∝ time
  left; registered via registerRound, loaded at startup
- shared: typed quiz payloads (QuizQuestionPayload / reveal truth / result)
- tests: quiz scoring, vote lock, bounds, reveal (bun test)

Client:
- room store handles round:start / voteAck / reveal / game:end
- RoomPage dispatches by status: lobby (host start controls) → quiz view
  (question, countdown, vote, reveal + scoreboard) → game-end view
- replaces standalone lobby page

Roadmap V1 step 4. Verified end-to-end over the wire (start→vote→reveal→
score→next round→game:end, no answer leaked, anti-repeat works).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:43:17 +02:00

59 lines
2.2 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { RoomManager } from "../../../rooms"
import type { RoundContext } from "../../round"
import { QuizRound } from "./quiz-round"
import { QUIZ_QUESTIONS } from "./questions"
const question = QUIZ_QUESTIONS[0] // "Link" → correctIndex 1
function makeCtx(): { ctx: RoundContext; p1: string; p2: string } {
const rooms = new RoomManager()
const { room, player: a } = rooms.create("Alice", "sa")
const { player: b } = rooms.join(room.code, "Bob", "sb")
const ctx: RoundContext = {
room,
djId: null,
votes: new Map(),
startedAt: 0,
endsAt: 10_000,
data: { question, votedAt: new Map() },
}
return { ctx, p1: a.id, p2: b.id }
}
describe("QuizRound", () => {
test("verrouille le premier vote (idempotent)", () => {
const round = new QuizRound(() => 1000)
const { ctx, p1 } = makeCtx()
round.submitAnswer(ctx, p1, { choiceIndex: 1 })
round.submitAnswer(ctx, p1, { choiceIndex: 0 }) // ignoré
expect(ctx.votes.get(p1)).toEqual({ choiceIndex: 1 })
})
test("ignore un index hors bornes", () => {
const round = new QuizRound(() => 1000)
const { ctx, p1 } = makeCtx()
round.submitAnswer(ctx, p1, { choiceIndex: 99 })
expect(ctx.votes.has(p1)).toBe(false)
})
test("reveal expose la vérité + le résultat par joueur", () => {
const round = new QuizRound(() => 1000)
const { ctx, p1, p2 } = makeCtx()
round.submitAnswer(ctx, p1, { choiceIndex: 1 }) // juste
round.submitAnswer(ctx, p2, { choiceIndex: 0 }) // faux
const { truth, perPlayerResult } = round.reveal(ctx)
expect(truth.correctIndex).toBe(question.correctIndex)
expect(perPlayerResult[p1]).toEqual({ choiceIndex: 1, correct: true })
expect(perPlayerResult[p2]).toEqual({ choiceIndex: 0, correct: false })
})
test("score = base + bonus rapidité pour les bonnes réponses uniquement", () => {
const round = new QuizRound(() => 1000) // vote à t=1000 sur 10000 → reste 90%
const { ctx, p1, p2 } = makeCtx()
round.submitAnswer(ctx, p1, { choiceIndex: 1 }) // juste, rapide
round.submitAnswer(ctx, p2, { choiceIndex: 0 }) // faux
const deltas = round.score(ctx)
expect(deltas).toEqual([{ playerId: p1, delta: 190 }]) // 100 + round(100 * 0.9)
})
})