1
0
Fork 0
polaris-task-force/tests/int/poker-dev-controls.int.spec.ts
Z8MB1E dce6f8f2ee fix(poker): dev panel card pickers ignored the first selection
CardPicker propagated null into onChange when no card was selected yet,
so the board picker's parent coerced every first pick to 2 of spades and
the hole-card pickers could never hold a value, keeping Queue hand
disabled forever. Picks now build a complete card, preserving any
already-chosen half, with regression tests on the extracted helper.

Co-authored-by: Sisyphus <sisyphus@ohmyopencode.dev>
2026-09-16 19:10:37 -04:00

163 lines
5.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
advanceState,
applyAction,
createTableState,
type PokerTableState,
} from "@/lib/minigames/poker";
import type { Card } from "@/lib/minigames/poker";
import { applyCardPick } from "@/components/frontend/minigames/poker/PokerDevPanel";
import {
adjustStack,
createTable,
getSnapshotFor,
joinTable,
setBoardQueue,
setHoleQueue,
startGame,
} from "@/lib/minigames/poker/tables";
function card(rank: Card["rank"], suit: Card["suit"]): Card {
return { rank, suit };
}
function withQueues(
state: PokerTableState,
queues: Partial<Pick<PokerTableState, "queuedBoard" | "queuedHoles">>,
): PokerTableState {
return { ...state, ...queues };
}
/** Apply an action and unwrap the result; throws on illegal actions. */
function act(
state: PokerTableState,
seatId: number,
action: Parameters<typeof applyAction>[2],
amount?: number,
): PokerTableState {
const result = applyAction(state, seatId, action, amount);
if (!result.success) throw new Error(result.error);
return result.state;
}
describe("dev board queue", () => {
it("deals the queued flop and turn cards in order before falling back to the deck", () => {
let state = createTableState([{ name: "A" }, { name: "B" }], {}, () => 0.5);
const queued = [card(14, "s"), card(13, "s"), card(12, "s"), card(11, "s"), card(10, "s")];
state = withQueues(state, { queuedBoard: queued });
// Preflop: the small blind calls to the big blind, the big blind checks,
// which completes the round and deals the flop from the queue.
state = act(state, 0, "call", undefined);
state = act(state, 1, "check", undefined);
expect(state.communityCards).toEqual([card(14, "s"), card(13, "s"), card(12, "s")]);
expect(state.queuedBoard).toEqual([card(11, "s"), card(10, "s")]);
// Flop: both check, dealing the turn from the queue.
state = act(state, 1, "check", undefined);
state = act(state, 0, "check", undefined);
expect(state.communityCards[3]).toEqual(card(11, "s"));
expect(state.queuedBoard).toEqual([card(10, "s")]);
});
it("consumes queued cards inside runOutAndShowdown when betting is impossible", () => {
let state = createTableState(
[
{ name: "A", stack: 50 },
{ name: "B", stack: 50 },
],
{},
() => 0.5,
);
state = withQueues(state, {
queuedBoard: [card(5, "h"), card(6, "h"), card(7, "h"), card(8, "h"), card(9, "h")],
});
// Two quick all-ins leave nobody able to act: the board runs out.
state = act(state, 0, "raise", 50);
state = act(state, 1, "call", undefined);
expect(state.communityCards).toEqual([
card(5, "h"),
card(6, "h"),
card(7, "h"),
card(8, "h"),
card(9, "h"),
]);
expect(state.queuedBoard).toEqual([]);
});
});
describe("dev hole queues", () => {
it("replaces the next hand's hole cards for queued seats only", () => {
let state = createTableState([{ name: "A" }, { name: "B" }, { name: "C" }], {}, () => 0.5);
// Queue while hand 1 is live, then fold it out so hand 2 deals fresh.
state = withQueues(state, {
queuedHoles: {
0: [card(14, "s"), card(14, "h")],
1: [card(2, "c"), card(3, "c")],
},
});
state = act(state, 0, "fold", undefined);
state = act(state, 1, "fold", undefined);
const dealt = advanceState(state);
expect(dealt.players[0].holeCards).toEqual([card(14, "s"), card(14, "h")]);
expect(dealt.players[1].holeCards).toEqual([card(2, "c"), card(3, "c")]);
expect(dealt.players[2].holeCards.length).toBe(2);
expect(dealt.queuedHoles).toEqual({});
});
});
describe("dev stack adjustment", () => {
it("adds and subtracts chips, clamped at zero", () => {
const tableId = createTable(9601, "Host");
joinTable(tableId, 9602, "Guest");
startGame(tableId, 9601);
let snapshot = getSnapshotFor(tableId, 9601);
const hostSeat = snapshot.you?.viewerSeat ?? 0;
const before = snapshot.you?.players.find((p) => p.seatId === hostSeat)?.stack ?? 0;
adjustStack(tableId, 9601, hostSeat, 250);
snapshot = getSnapshotFor(tableId, 9601);
expect(snapshot.you?.players.find((p) => p.seatId === hostSeat)?.stack).toBe(before + 250);
adjustStack(tableId, 9601, hostSeat, -(before + 250) - 500);
snapshot = getSnapshotFor(tableId, 9601);
expect(snapshot.you?.players.find((p) => p.seatId === hostSeat)?.stack).toBe(0);
});
it("rejects invalid seats and oversized queues", () => {
const tableId = createTable(9611, "Host");
joinTable(tableId, 9612, "Guest");
startGame(tableId, 9611);
expect(() =>
setBoardQueue(tableId, 9611, [
card(2, "s"),
card(3, "s"),
card(4, "s"),
card(5, "s"),
card(6, "s"),
card(7, "s"),
]),
).toThrow();
expect(() => setHoleQueue(tableId, 9611, 9, [card(2, "s"), card(3, "s")])).toThrow();
expect(() => adjustStack(tableId, 9611, 9, 100)).toThrow();
});
});
describe("dev panel card picker", () => {
it("builds a complete card from a first rank pick and preserves the chosen suit", () => {
expect(applyCardPick(null, "rank", "13")).toEqual(card(13, "s"));
expect(applyCardPick(card(13, "h"), "rank", "14")).toEqual(card(14, "h"));
});
it("builds a complete card from a first suit pick and preserves the chosen rank", () => {
expect(applyCardPick(null, "suit", "c")).toEqual(card(2, "c"));
expect(applyCardPick(card(14, "h"), "suit", "d")).toEqual(card(14, "d"));
});
});