import { describe, expect, it } from "vitest"; import type { Card, PublicPlayerView } from "@/lib/minigames/blackjack"; import { soundEventsForTransition, type BlackjackSoundEvent, type BlackjackSoundSnapshot, } from "@/components/frontend/minigames/blackjack/blackjackSoundEvents"; const card = (rank: Card["rank"], suit: Card["suit"]): Card => ({ rank, suit }); function player(seatId: number, overrides: Partial = {}): PublicPlayerView { return { seatId, name: `Player ${seatId + 1}`, bankroll: 1000, hands: [], insuranceBet: 0, insuranceAnswered: false, hasBet: false, sittingOut: false, bankrupt: false, left: false, roundsPlayed: 0, roundsWon: 0, blackjacksHit: 0, ...overrides, }; } function hand( overrides: Partial = {}, ): PublicPlayerView["hands"][number] { return { cards: [], bet: 25, status: "active", doubled: false, fromSplit: false, splitAces: false, total: 21, outcome: null, netChange: null, ...overrides, }; } function view( overrides: Partial = {}, ): BlackjackSoundSnapshot["you"] { return { roundNumber: 1, phase: "betting", players: [player(0), player(1)], dealer: { cards: [], holeHidden: true, total: null, hasBlackjack: null }, roundResult: null, viewerSeat: 0, yourTurn: false, yourHands: [], ...overrides, }; } function snapshot( version: number, nextView: BlackjackSoundSnapshot["you"] = view(), ): BlackjackSoundSnapshot { return { version, you: nextView }; } function byType(events: readonly BlackjackSoundEvent[], type: BlackjackSoundEvent["type"]) { return events.find((event) => event.type === type); } describe("blackjack sound transition events", () => { it("plays a round-start chime when the round counter advances", () => { const events = soundEventsForTransition( snapshot(1, view({ roundNumber: 1, phase: "round-complete" })), snapshot(2, view({ roundNumber: 2, phase: "betting" })), ); expect(byType(events, "round-start")).toBeDefined(); }); it("announces turn changes", () => { const events = soundEventsForTransition( snapshot(1, view({ yourTurn: false })), snapshot(2, view({ yourTurn: true, phase: "player" })), ); expect(byType(events, "turn-gained")).toBeDefined(); const back = soundEventsForTransition( snapshot(2, view({ yourTurn: true, phase: "player" })), snapshot(3, view({ yourTurn: false, phase: "player" })), ); expect(byType(back, "turn-lost")).toBeDefined(); }); it("announces cards hitting the felt", () => { const dealtCards = [card(10, "s"), card(9, "h")]; const events = soundEventsForTransition( snapshot(1, view()), snapshot( 2, view({ phase: "player", players: [player(0, { hasBet: true, hands: [hand({ cards: dealtCards })] }), player(1)], dealer: { cards: [card(6, "d"), card(7, "c")], holeHidden: true, total: null, hasBlackjack: null }, }), ), ); const deal = byType(events, "cards-dealt"); expect(deal).toBeDefined(); if (deal?.type === "cards-dealt") expect(deal.cardCount).toBe(4); }); it("announces a wager and a bust", () => { const events = soundEventsForTransition( snapshot(1, view({ players: [player(0, { hands: [hand()] }), player(1)] })), snapshot( 2, view({ players: [player(0, { hasBet: true, hands: [hand({ cards: [card(5, "s"), card(6, "s")] })] }), player(1)], }), ), ); expect(byType(events, "wager")).toBeDefined(); const bustEvents = soundEventsForTransition( snapshot( 3, view({ phase: "player", players: [player(0, { hasBet: true, hands: [hand({ cards: [card(5, "s"), card(6, "s")] })] }), player(1)], }), ), snapshot( 4, view({ phase: "player", players: [ player(0, { hasBet: true, hands: [hand({ status: "bust", cards: [card(5, "s"), card(6, "s"), card(10, "d")] })], }), player(1), ], }), ), ); expect(byType(bustEvents, "bust")).toBeDefined(); }); it("settles with a viewer result event", () => { const settled = view({ phase: "round-complete", roundResult: { roundNumber: 1, dealerTotal: 19, dealerBusted: false, dealerHasBlackjack: false, hands: [ { seatId: 0, handIndex: 0, total: 20, outcome: "win", payout: 25, netChange: 25 }, ], insurance: [], }, }); const events = soundEventsForTransition(snapshot(1, view()), snapshot(2, settled)); expect(byType(events, "settle")).toBeDefined(); const result = byType(events, "hand-result"); expect(result).toBeDefined(); if (result?.type === "hand-result") { expect(result.result).toBe("win"); expect(result.netChange).toBe(25); } }); it("reports a push result on a tie", () => { const settled = view({ phase: "round-complete", roundResult: { roundNumber: 1, dealerTotal: 20, dealerBusted: false, dealerHasBlackjack: false, hands: [{ seatId: 0, handIndex: 0, total: 20, outcome: "push", payout: 0, netChange: 0 }], insurance: [], }, }); const events = soundEventsForTransition(snapshot(1, view()), snapshot(2, settled)); const result = byType(events, "hand-result"); expect(result).toBeDefined(); if (result?.type === "hand-result") expect(result.result).toBe("push"); }); it("returns no events for the same version", () => { const current = snapshot(5, view()); expect(soundEventsForTransition(current, current)).toEqual([]); }); });