Files
JoshHeaps.Net/tests/holdem/sidebarPanels.test.js
T
jheapsandClaude Opus 5 6eb97c8a35 Give the sidebar eight names instead of five and a cupboard
"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 a navigation
layer with more than one logic in it.

It measured 1206px and 103 visible elements, against 555 and 29 for the
next biggest, and "The table itself" sat 1052px down inside it. Reaching
Reset table meant opening a panel called Session and scrolling past the
money and the log to get to it.

It is now Standings, Waiting list, Hands and Save & reset, each named for
the thing a dealer came looking for, in the order of a night: set the
table up, watch the money, fill the seats, look back at a hand — and,
last and apart, the two buttons that end it. Reset table goes from 1052px
inside a panel called Session to 62px inside one whose label says what it
does. The biggest panel drops from 1206px to 607px. The four subheads
that used to carry those names are gone, because the summary says it now.

Every panel name is also a heading. A <summary> is announced as a button,
so the sidebar's names — its whole navigation — were unreachable by
heading, and splitting Session would have removed the only four headings
down there. The outline is now h1 page, h2 per panel, h3 for the two
subheads that remain. Visually identical: the h2 gives up everything a
heading brings with it.

The cost is 216px more collapsed summaries at rest, three more one-line
labels. Audit unchanged at 2 failing configurations and 3 offenders, both
pre-existing. 795 unit tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-05 21:52:34 -06:00

125 lines
6.0 KiB
JavaScript

// 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(/<details class="sidebarPanel"/)
.slice(1)
.map(chunk => {
const name = chunk.match(/<summary>\s*(?:<h2>)?([^<]+)/);
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 &amp; reset",
]);
});
test("and each one is a heading, not only a button", () => {
// A <summary> 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, /<summary><h2>/,
`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 &amp; reset");
assert.equal(panelHolding("loadTableBtn"), "Save &amp; reset");
assert.equal(panelHolding("resetTableBtn"), "Save &amp; reset");
assert.equal(panels()[panels().length - 1].name, "Save &amp; 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(/<h3 class="panelSubhead">([^<]+)</g)]
.map(m => 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(/<h3 class="panelSubhead">([^<]+)</g)]
.map(m => `${p.name}: ${m[1].trim()}`));
assert.deepEqual(kept, ["Standings: Settle up", "Chip Legend: Stacks in chips"]);
});
});