const { test, describe } = require("node:test"); const assert = require("node:assert/strict"); const { Betting, makeTable, chipsOnTable } = require("./load"); describe("Betting.beginHand", () => { test("blinds are posted and the seat after the big blind acts first", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], dealerSeatIndex: 0, smallBlind: 1, bigBlind: 2 }); Betting.beginHand(state); assert.equal(state.seats[1].committed, 1, "small blind"); assert.equal(state.seats[2].committed, 2, "big blind"); assert.equal(state.currentBet, 2); assert.equal(state.actingSeatIndex, 3, "under the gun"); }); test("heads-up, the button posts the small blind and acts first pre-flop", () => { const state = makeTable({ seatCount: 6, seated: [2, 5], dealerSeatIndex: 2 }); Betting.beginHand(state); assert.equal(state.seats[2].committed, 1, "the button is the small blind"); assert.equal(state.seats[5].committed, 2); assert.equal(state.actingSeatIndex, 2, "the small blind acts first pre-flop heads-up"); }); test("heads-up, the big blind acts first once the flop is out", () => { const state = makeTable({ seatCount: 6, seated: [2, 5], dealerSeatIndex: 2 }); Betting.beginHand(state); Betting.call(state, 2); Betting.check(state, 5); Betting.closeStreet(state); assert.equal(state.street, "flop"); assert.equal(state.actingSeatIndex, 5, "the big blind is first to act after the flop"); }); test("a blind bigger than a stack puts that player all-in without stalling the action", () => { const state = makeTable({ seatCount: 6, seated: [0, 1], dealerSeatIndex: 0, smallBlind: 50, bigBlind: 100 }); state.seats[0].stack = 10; // can't cover the small blind Betting.beginHand(state); assert.equal(state.seats[0].stack, 0, "all-in for what they had"); assert.equal(state.seats[0].committed, 10); assert.notEqual(state.actingSeatIndex, 0, "a player with no chips has nothing to decide"); }); test("posting blinds never invents or destroys chips", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], stack: 100 }); const before = chipsOnTable(state); Betting.beginHand(state); assert.equal(chipsOnTable(state), before); }); }); describe("Betting street completion", () => { test("a street ends once everyone has matched the bet and had a turn", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, 3); Betting.call(state, 0); Betting.call(state, 1); assert.equal(Betting.isStreetComplete(state), false, "the big blind still has an option"); Betting.check(state, 2); assert.equal(Betting.isStreetComplete(state), true); assert.equal(state.actingSeatIndex, null); }); test("a raise reopens the action for everyone who had already called", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, 3); Betting.call(state, 0); assert.equal(state.seats[3].hasActed, true); Betting.raiseTo(state, 1, 10); assert.equal(state.seats[3].hasActed, false, "seat 3 owes a response again"); assert.equal(state.seats[0].hasActed, false); assert.equal(state.currentBet, 10); assert.equal(Betting.isStreetComplete(state), false); }); test("the action skips a player who is all-in but still in the hand", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); state.seats[3].stack = 5; Betting.beginHand(state); Betting.allIn(state, 3); assert.equal(state.seats[3].stack, 0); assert.notEqual(state.actingSeatIndex, 3); // They are still live and can still win the pot. assert.ok(Betting.liveSeats(state).some(s => s.index === 3)); }); test("closing a street sweeps chips into hand totals and resets the bet", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, 3); Betting.call(state, 0); Betting.call(state, 1); Betting.check(state, 2); const before = chipsOnTable(state); Betting.closeStreet(state); assert.equal(state.currentBet, 0); assert.equal(state.street, "flop"); assert.equal(chipsOnTable(state), before, "sweeping moves chips, it doesn't change the total"); state.seats.forEach(p => assert.equal(p.committed, 0)); assert.equal(Betting.handTotal(state.seats[2]), 2); }); }); describe("Betting all-in run-outs", () => { test("with everyone all-in there is nobody left to ask", () => { const state = makeTable({ seatCount: 3, seated: [0, 1, 2], stack: 20, dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.allIn(state, 2); Betting.allIn(state, 0); Betting.allIn(state, 1); assert.equal(Betting.canStillBet(state), false); assert.equal(Betting.isRunningOut(state), true, "the board just gets dealt out"); Betting.closeStreet(state); assert.equal(state.actingSeatIndex, null, "no phantom action on the flop"); }); test("one player with chips against an all-in cannot bet alone", () => { const state = makeTable({ seatCount: 3, seated: [0, 1, 2], dealerSeatIndex: 0 }); state.seats[1].stack = 5; Betting.beginHand(state); Betting.fold(state, 2); Betting.allIn(state, 0); Betting.allIn(state, 1); assert.equal(Betting.canStillBet(state), false); assert.equal(Betting.isHandOver(state), false, "two players are still live for the showdown"); }); test("everyone folding to one player ends the hand", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.fold(state, 3); Betting.fold(state, 0); Betting.fold(state, 1); assert.equal(Betting.isHandOver(state), true); }); }); describe("Betting money handling", () => { test("a player can never put in more than they have", () => { const state = makeTable({ seatCount: 3, seated: [0, 1, 2], stack: 30, dealerSeatIndex: 0 }); Betting.beginHand(state); const moved = Betting.putIn(state, 0, 500); assert.equal(moved, 30); assert.equal(state.seats[0].stack, 0); }); test("raise-to is what a dealer says out loud, not raise-by", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.raiseTo(state, 3, 10); assert.equal(state.seats[3].committed, 10, "their street total is 10, not 10 on top"); assert.equal(state.seats[3].stack, 90); }); test("the minimum raise has to match the last one", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); assert.equal(Betting.minRaiseTo(state), 4, "a big blind of 2 makes the first raise 4"); Betting.raiseTo(state, 3, 10); assert.equal(Betting.minRaiseTo(state), 18, "an 8-chip raise means the next one is to 18"); }); test("winnings land back in front of the player", () => { const state = makeTable({ seatCount: 3, seated: [0, 1, 2], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.creditWinnings(state, [{ id: state.seats[0].id, amount: 42.5 }]); assert.equal(state.seats[0].stack, 142.5, "the button posts nothing three-handed"); }); test("fractional blinds don't drift into floating-point dust", () => { const state = makeTable({ seatCount: 3, seated: [0, 1, 2], stack: 10, smallBlind: 0.1, bigBlind: 0.2 }); Betting.beginHand(state); Betting.call(state, 0); // button limps Betting.call(state, 1); // small blind completes Betting.check(state, 2); // big blind is happy Betting.closeStreet(state); assert.equal(chipsOnTable(state), 30, "0.1 + 0.2 arithmetic didn't lose a chip"); assert.equal(Betting.potSoFar(state), 0.6); state.seats.forEach(p => assert.equal(p.stack, 9.8)); }); });