// What the sidebar is a list of. // // The panels were Table Setup, Session, Chip Legend, Keyboard and Table Notes, and // "Session" was a container word rather than a name. It held the standings, a waiting // list, the hand log and the save/load/reset buttons — a topic, a task, a log and three // utilities under one label, which is the "don't mix apples and oranges" failure: a // navigation layer with more than one logic in it. Measured at 1206px and 103 visible // elements, against 555 and 29 for the next biggest, and "The table itself" sat 1052px // down inside it, so reaching Reset table meant opening a panel called Session and // scrolling past the money and the log. // // This is a markup test because that is where the fault lived. Nothing was broken — // every control worked and every id resolved — so nothing failed. What was wrong was // which panel a thing was in and what that panel was called. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const fs = require("fs"); const path = require("path"); const PAGE = fs.readFileSync( path.join(__dirname, "..", "..", "JoshHeaps.Net", "Pages", "Casino", "TexasHoldem.cshtml"), "utf8"); // A sidebar panel never contains another one — Table Setup's sub-panels carry a // different class — so splitting on the opening tag is enough to say which panel any // piece of markup is in, without parsing the document. const panels = () => PAGE .split(/
{ const name = chunk.match(/\s*(?:

)?([^<]+)/); return { name: name ? name[1].trim() : "(no summary)", markup: chunk }; }); const panelHolding = id => { const found = panels().filter(p => p.markup.includes(`id="${id}"`)); assert.equal(found.length, 1, `${id} is in ${found.length} panels, expected exactly 1`); return found[0].name; }; // --------------------------------------------------------------------------- describe("the sidebar's names", () => { test("say what is inside them", () => { // Every one of these is a thing a dealer goes looking for. None of them is a // word that could hold anything — which is the test "Session" fails. assert.deepEqual(panels().map(p => p.name), [ "Table Setup", "Standings", "Waiting list", "Hands", "Chip Legend", "Keyboard", "Table Notes", "Save & reset", ]); }); test("and each one is a heading, not only a button", () => { // A is announced as a button. The sidebar's names are the whole of its // navigation, so without a heading inside, none of them is reachable by anyone // moving through the page by heading — and splitting Session removed the four // subheads that used to be the only headings down here. panels().forEach(p => assert.match(p.markup, /

/, `the ${p.name} panel's name is not a heading`)); }); }); describe("where things live", () => { test("the money is together, and the warning about losing it is with it", () => { assert.equal(panelHolding("standingsList"), "Standings"); assert.equal(panelHolding("settleUp"), "Standings"); assert.equal(panelHolding("bankReconcile"), "Standings"); // renderStorageWarning() says this "sits at the top of the panel a dealer opens // to check the money". That panel is now called Standings. assert.equal(panelHolding("storageWarning"), "Standings"); }); test("the queue for a seat is its own panel", () => { assert.equal(panelHolding("waitingList"), "Waiting list"); assert.equal(panelHolding("waitingListForm"), "Waiting list"); }); test("the log and the two ways out of it are together", () => { assert.equal(panelHolding("handHistoryList"), "Hands"); assert.equal(panelHolding("handHistoryFilter"), "Hands"); assert.equal(panelHolding("exportHistoryBtn"), "Hands"); assert.equal(panelHolding("clearHistoryBtn"), "Hands"); }); test("and the file is last, with the danger on the label", () => { assert.equal(panelHolding("saveTableBtn"), "Save & reset"); assert.equal(panelHolding("loadTableBtn"), "Save & reset"); assert.equal(panelHolding("resetTableBtn"), "Save & reset"); assert.equal(panels()[panels().length - 1].name, "Save & reset", "the panel that ends the night belongs at the end, away from the working ones"); }); }); describe("no panel goes back to being a grab-bag", () => { // Not a size limit — Table Setup is legitimately long and Chip Legend legitimately // repeats a row per chip. What this counts is *subjects*: a panel that needs its own // headings to say what its parts are is a panel holding more than one thing, which // is what Session was and how it got that way. test("at most one subject needs a heading inside a panel", () => { panels().forEach(p => { const subheads = [...p.markup.matchAll(/

([^<]+) m[1].trim()); assert.ok(subheads.length <= 1, `${p.name} has ${subheads.length} sections inside it (${subheads.join(", ")}) — ` + `if a panel needs headings to list its parts, its name is not doing the work`); }); }); test("and the ones that remain are a detail of their panel, not a second subject", () => { // Settle up is the standings once the night ends; Stacks in chips is the chip // legend counted out. Both are the same subject at a different moment, which is // the case a subhead is for. const kept = panels().flatMap(p => [...p.markup.matchAll(/

([^<]+) `${p.name}: ${m[1].trim()}`)); assert.deepEqual(kept, ["Standings: Settle up", "Chip Legend: Stacks in chips"]); }); });