// The hand-history panel is only rebuilt when its signature changes, which takes a // full re-render from ~30ms to ~3ms. The whole saving depends on that signature // noticing every way the log can actually change — a stale panel would have the // dealer reading last hand's payouts while settling this one. const { test, describe } = require("node:test"); const assert = require("node:assert/strict"); const { Render, HoldemState, makeTable } = require("./load"); // historySignature() reads Render.state, so point it at a table for each case. const signatureOf = state => { Render.state = state; return Render.historySignature(); }; const tableWithHands = (count) => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2] }); for (let i = 0; i < count; i++) { state.seats[0].amountOverride = 2; state.seats[1].amountOverride = 2; HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 4 }]); } return state; }; describe("the history panel knows when to redraw", () => { test("nothing changes while a hand is being played", () => { const state = tableWithHands(3); const before = signatureOf(state); // A whole street of betting touches no part of the log. state.seats[0].committed = 5; state.seats[1].folded = true; state.actingSeatIndex = 2; state.street = "flop"; assert.equal(signatureOf(state), before, "betting must not force a rebuild"); }); test("settling a hand changes it", () => { const state = tableWithHands(3); const before = signatureOf(state); HoldemState.recordHand(state, [{ id: state.seats[1].id, label: "Ben", amount: 12 }]); assert.notEqual(signatureOf(state), before); }); test("clearing the history changes it", () => { const state = tableWithHands(5); const before = signatureOf(state); HoldemState.clearHistory(state); assert.notEqual(signatureOf(state), before); }); test("undoing back to a shorter log changes it", () => { const state = tableWithHands(2); const snapshot = JSON.parse(JSON.stringify(state)); HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 30 }]); const afterRecord = signatureOf(state); // The undo path swaps the state's contents in place. Object.keys(state).forEach(k => delete state[k]); Object.assign(state, snapshot); assert.notEqual(signatureOf(state), afterRecord, "an undone hand must disappear from the panel"); }); test("two hands that differ only in payout are told apart", () => { const a = tableWithHands(1); const b = tableWithHands(1); // Same count and hand number; different pot. b.handHistory[0].pot = a.handHistory[0].pot + 25; assert.notEqual(signatureOf(a), signatureOf(b)); }); test("the hand counter moving on changes it", () => { // The counter badge is drawn by the same function, so a new hand number has // to invalidate even when the log itself is untouched. const state = tableWithHands(2); const before = signatureOf(state); state.handNumber += 1; assert.notEqual(signatureOf(state), before); }); test("an empty log has a stable signature of its own", () => { const state = makeTable({ seatCount: 4, seated: [0, 1] }); const first = signatureOf(state); assert.equal(signatureOf(state), first); HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 4 }]); assert.notEqual(signatureOf(state), first, "the first hand ever must draw the panel"); }); }); describe("the hand-time cache", () => { test("the same timestamp is only formatted once, and formats the same", () => { Render.handTimeCache.clear(); const stamp = Date.UTC(2026, 8, 1, 15, 4); const first = Render.formatHandTime(stamp); assert.equal(Render.handTimeCache.size, 1); const second = Render.formatHandTime(stamp); assert.equal(second, first); assert.equal(Render.handTimeCache.size, 1, "not re-formatted"); }); test("different timestamps still format differently", () => { Render.handTimeCache.clear(); const a = Render.formatHandTime(Date.UTC(2026, 8, 1, 15, 4)); const b = Render.formatHandTime(Date.UTC(2026, 8, 1, 18, 47)); assert.notEqual(a, b); assert.equal(Render.handTimeCache.size, 2); }); test("a nonsense timestamp doesn't throw", () => { Render.handTimeCache.clear(); assert.doesNotThrow(() => Render.formatHandTime(undefined)); assert.doesNotThrow(() => Render.formatHandTime("not a date")); }); });