// The bar's pot list is not allowed to grow with the table. // // #actionBar is fixed over the felt, so every pixel it takes is a pixel the table // loses — and this list took one row per all-in with no ceiling. Measured on a 360x640 // phone, five players idle: bar 151px. Six all-in: 303px. Twelve all-in: 411px, which // is 64% of the screen, leaving 96px of felt to draw twelve seats in. A fixed overlay // whose height depends on the state is a fault whatever it is showing, so the cap is // checked here rather than left to whoever next adds an all-in to the audit sweep. // // The two ends survive because they are the two that are live. Side pots are paid from // the newest one back down to the main pot, so the top layer is the next thing that // happens and the main pot is the last, and those are the two a dealer says out loud // while the chips are stacked — "main pot 7,200, side pot 4,500". The layers between // are the middle of that count-down, wanted at showdown, which is the pot wizard, which // passes no cap and still lists all of them. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Render, Money, PotCalculator } = require("./load"); const nameFor = id => `P${id}`; // The same stub potBreakdown.test.js uses — enough of an element to say what the row // is made of, and no jsdom in a suite that runs in a second. const fakeDom = () => { const make = () => ({ className: "", textContent: "", title: "", children: [], classList: { add() {} }, appendChild(child) { this.children.push(child); return child; }, }); global.document = { createElement: make }; return () => { delete global.document; }; }; // A table where everyone is all-in for a different amount, which is the shape that // makes the most pot layers — the worst case and the one that was measured. `count` // players ladder into `count - 1` layers: two of them share a single undivided pot and // the breakdown declines to draw at all, which is why nothing below three appears here. const laddered = count => PotCalculator.layers( Array.from({ length: count }, (_, i) => ({ id: `s${i}`, contribution: 100 + i * 130, folded: false, }))); const build = (pots, options) => { const undo = fakeDom(); try { const wrap = Render.buildPotBreakdown(pots, [], nameFor, null, options); return wrap.children.map(row => ({ className: row.className, label: row.children[0].textContent, amount: row.children[1].textContent, who: row.children[2].textContent, title: row.children[2].title, })); } finally { undo(); } }; describe("the bar's copy of the pot list", () => { test("stops growing once there are more pots than it has room for", () => { // The property that matters: rows are a function of the cap, not of the table. // Twelve-handed and six-handed have to render the same number of lines. for (let players = 3; players <= 12; players++) { const rows = build(laddered(players), { maxLayers: 4 }); assert.ok(rows.length <= 4, `${players} all-in drew ${rows.length} rows; the bar is sized for 4`); } }); test("and the number of rows never depends on how many people are all-in", () => { const wide = [8, 9, 10, 11, 12].map(n => build(laddered(n), { maxLayers: 4 }).length); assert.deepEqual(wide, [3, 3, 3, 3, 3], "past the cap every table should draw main, the elision and the top pot"); }); test("keeps the two pots a dealer actually announces", () => { const pots = laddered(12); const rows = build(pots, { maxLayers: 4 }); assert.equal(rows[0].label, pots[0].label, "the main pot is the last one paid"); assert.equal(rows[rows.length - 1].label, pots[pots.length - 1].label, "the newest side pot is the first one paid"); }); test("and leaves them in the order the pots were made", () => { const rows = build(laddered(12), { maxLayers: 4 }); assert.match(rows[1].className, /potLayerMore/, "the elision belongs where the gap is, not appended after the list"); }); }); describe("the money in the rows that were elided", () => { test("is still on the screen, added up", () => { // The one thing a summary row must not do is lose chips. Whatever is drawn has // to account for every pot, or a dealer counting the announcement against the // chips in the middle finds a shortfall the tool caused. for (let players = 3; players <= 12; players++) { const pots = laddered(players); const total = pots.reduce((sum, pot) => sum + pot.amount, 0); const drawn = build(pots, { maxLayers: 4 }) .reduce((sum, row) => sum + Number(row.amount.replace(/[^0-9.]/g, "")), 0); assert.equal(drawn, total, `${players} all-in: rows add to ${drawn}, the pots add to ${total}`); } }); test("and says how many it stands for", () => { const rows = build(laddered(12), { maxLayers: 4 }); const pots = laddered(12); assert.equal(rows[1].label, `${pots.length - 2} more side pots`); }); test("with the list itself in a title, for anyone with a pointer", () => { const pots = laddered(12); const rows = build(pots, { maxLayers: 4 }); pots.slice(1, -1).forEach(pot => { assert.ok(rows[1].title.includes(pot.label), `${pot.label} vanished without trace`); assert.ok(rows[1].title.includes(Money.format(pot.amount)), `${pot.label}'s amount vanished without trace`); }); }); test("and points at where the whole list is", () => { // The title is a pointer affordance, so it cannot be the answer on a phone. The // visible text has to say where the rest lives. assert.match(build(laddered(12), { maxLayers: 4 })[1].who, /resolve/i); }); }); describe("when eliding would not buy anything", () => { test("a list that fits is left alone", () => { for (let players = 3; players <= 5; players++) { const pots = laddered(players); assert.ok(pots.length <= 4, "this loop is meant to cover the fitting sizes"); const rows = build(pots, { maxLayers: 4 }); assert.equal(rows.length, pots.length); assert.deepEqual(rows.map(r => r.label), pots.map(p => p.label)); } }); test("and a single middle layer is not replaced by a line saying there is one", () => { // At exactly the cap the middle is one row, and swapping one row for one row is // pure indirection: the dealer loses an amount and gains a sentence. const pots = laddered(5); assert.equal(pots.length, 4, "five all-in ladder into exactly the cap"); const rows = build(pots, { maxLayers: 4 }); assert.equal(rows.length, 4); assert.ok(!rows.some(r => /potLayerMore/.test(r.className))); }); }); describe("the wizard, which has room", () => { test("still gets every layer", () => { // Same builder, no cap — this is the announcement read out before anybody shows // a hand, and it is in a dialog that scrolls. Capping it there would hide the // pots at the moment they are being paid. const pots = laddered(12); const rows = build(pots, undefined); assert.equal(rows.length, pots.length); assert.ok(!rows.some(r => /potLayerMore/.test(r.className))); }); });