The sheet a dealer opens mid-argument held twelve rulings with all their prose showing — 2,480px of it on a 390px phone, of which 1,754 was below the fold — and a row of twelve chips across the top to jump between them. The chips were an index of the twelve headings printed directly above the same twelve headings. The same list twice: 172px of navigation before the first word, and the dealer who tapped one landed in the middle of a scroll with eleven rulings they hadn't asked for still wrapped around them. The headings are the index now. NN/g's rule for this shape — a longer list of independent topics somebody opens one of — is an accordion, shut by default, and it suits these unusually well because half of them answer the question without being opened at all. "A short all-in doesn't reopen the betting" is the ruling, not the topic. So is "Chips nobody called come back", and "A straddle is a blind raise", and "A raise has to be a full raise". Measured after: 29px below the fold on a 390px phone with all twelve rows on screen at once, 51px on a desktop, and 244px with the longest of them open. Not exclusive — no `name` attribute. Closing one because another opened moves everything under the finger that is reaching, which is the disorientation the accordion is here to remove rather than add. And the sheet shuts what it finds open when it is opened again: <details> keeps its state, so without that a dealer who read the odd-chip rule an hour ago finds it still hanging open under the one they came back for. Two things that came with it: The gold arrives when a ruling is opened and not before. Twelve gold headings in a column is the accent doing the job of body text, which leaves it nothing to say when one of them is the one being read. The sidebar's eight panel names are --text for the same reason; this is the same component doing the same job. Five of the twelve are two paragraphs, and they ran into each other — the sections were spaced from one another but nothing separated the halves of one ruling. It never showed while there was a gold heading every few lines to break the column up. 1095 tests pass; the 418-audit sweep is clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
143 lines
6.8 KiB
JavaScript
143 lines
6.8 KiB
JavaScript
// The sheet a dealer opens mid-argument.
|
|
//
|
|
// It held twelve rulings with all their prose showing — 2,480px of it on a 390px
|
|
// phone, of which 1,754 was below the fold — and a row of twelve chips across the top
|
|
// to jump between them. The chips were an index of the twelve headings printed directly
|
|
// above the same twelve headings: the same list twice, 172px of navigation before the
|
|
// first word, and the dealer who used one landed in the middle of a scroll with eleven
|
|
// rulings they hadn't asked for still wrapped around them.
|
|
//
|
|
// The headings are the index now. NN/g's rule for this shape — a longer list of
|
|
// independent topics somebody opens one of — is an accordion, shut by default, and
|
|
// half of these headings answer the question without being opened at all: "A short
|
|
// all-in doesn't reopen the betting" is the ruling, not the topic. Measured after:
|
|
// 29px below the fold on an iPhone with all twelve rows on screen at once, 51px on a
|
|
// desktop. Opening the longest of them leaves 244px.
|
|
//
|
|
// These are layout and markup decisions, so they are checked as text — except the one
|
|
// about what the sheet does when it is opened again, which is behaviour and lives in
|
|
// main.js.
|
|
const test = require("node:test");
|
|
const { describe } = require("node:test");
|
|
const assert = require("node:assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..", "JoshHeaps.Net");
|
|
const PAGE = fs.readFileSync(path.join(ROOT, "Pages", "Casino", "TexasHoldem.cshtml"), "utf8");
|
|
const CSS = fs.readFileSync(path.join(ROOT, "wwwroot", "css", "casino", "holdem.css"), "utf8");
|
|
const MAIN = fs.readFileSync(path.join(ROOT, "wwwroot", "js", "casino", "holdem", "main.js"), "utf8");
|
|
|
|
const sheet = () => {
|
|
const at = PAGE.indexOf('<div id="rulingsOverlay"');
|
|
assert.ok(at >= 0, "there is no rulings overlay");
|
|
return PAGE.slice(at, PAGE.indexOf('<div id="seatMenuOverlay"', at));
|
|
};
|
|
|
|
const ruleFor = selector => {
|
|
const at = CSS.indexOf(`\n${selector} {`);
|
|
assert.ok(at >= 0, `there is no \`${selector}\` rule`);
|
|
return CSS.slice(at, CSS.indexOf("}", at));
|
|
};
|
|
|
|
// Each ruling as its own slice of markup.
|
|
const rulings = () => {
|
|
const parts = sheet().split('<details class="ruling"');
|
|
return parts.slice(1).map(p => p.slice(0, p.indexOf("</details>")));
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("twelve rulings, shut", () => {
|
|
test("every one of them is a disclosure, and there are still twelve", () => {
|
|
assert.equal(rulings().length, 12);
|
|
});
|
|
|
|
// NN/g is explicit that opening the first one by default costs the reader the
|
|
// glance at the whole list, which on this sheet is the entire point.
|
|
test("none of them is open in the markup", () => {
|
|
assert.ok(!/<details class="ruling"[^>]*\bopen\b/.test(sheet()),
|
|
"a ruling starts open, so the sheet no longer opens on its own contents");
|
|
});
|
|
|
|
// Native exclusive accordions close the open one when another is opened, which
|
|
// shifts everything below it under the finger that is reaching. That is the
|
|
// disorientation the accordion is here to remove, not add.
|
|
test("opening one does not close another", () => {
|
|
assert.ok(!/<details class="ruling"[^>]*\bname=/.test(sheet()),
|
|
"the rulings are an exclusive accordion, so content moves when one opens");
|
|
});
|
|
|
|
test("the name of a ruling is the control that opens it", () => {
|
|
rulings().forEach((r, i) => {
|
|
assert.match(r, /<summary><h3>.+<\/h3><\/summary>/,
|
|
`ruling ${i + 1} does not carry its heading in its summary`);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("the index that was above them is gone, not merely hidden", () => {
|
|
test("nothing builds a chip row any more", () => {
|
|
assert.ok(!/rulingsIndex/.test(PAGE), "the index nav is still in the markup");
|
|
assert.ok(!/buildRulingsIndex|rulingsIndexBtn/.test(MAIN), "main.js still builds the index");
|
|
assert.ok(!/rulingsIndexBtn/.test(CSS), "the index chips still have styles");
|
|
});
|
|
|
|
// The short labels existed only to fit three chips on a line. With the full
|
|
// headings doing the work there is nothing left to read them.
|
|
test("and the short labels it read are gone with it", () => {
|
|
assert.ok(!/data-short=/.test(PAGE));
|
|
});
|
|
});
|
|
|
|
describe("what a summary has to be to work", () => {
|
|
// A <summary> is announced as a button and not as a heading, so without this the
|
|
// twelve names — the whole of this sheet's navigation — are invisible to anybody
|
|
// moving through it by heading. Same fix as the sidebar's panel names.
|
|
test("the heading survives being a control", () => {
|
|
assert.match(ruleFor(".ruling > summary > h3"), /display:\s*inline/);
|
|
assert.match(ruleFor(".ruling > summary > h3"), /font:\s*inherit/);
|
|
});
|
|
|
|
// A <summary> is content-box, so a min-height is *added* to its padding — the trap
|
|
// the sidebar's headers were in, one stylesheet section over.
|
|
test("a thumb gets its 44px, counted the way the box actually counts", () => {
|
|
const rule = ruleFor(".ruling > summary");
|
|
assert.match(rule, /box-sizing:\s*border-box/);
|
|
assert.match(rule, /min-height:\s*44px/);
|
|
});
|
|
|
|
test("the accent belongs to the ruling being read, not to all twelve", () => {
|
|
assert.match(ruleFor(".ruling > summary"), /color:\s*var\(--text\)/);
|
|
assert.match(ruleFor(".ruling[open] > summary"), /color:\s*var\(--gold\)/);
|
|
});
|
|
|
|
test("and so does the gold bar down the side", () => {
|
|
assert.match(ruleFor(".ruling[open]"), /border-left:\s*2px solid/);
|
|
assert.ok(!/border-left/.test(ruleFor(".ruling")),
|
|
"every ruling wears the open one's marker");
|
|
});
|
|
});
|
|
|
|
describe("reading one of them", () => {
|
|
// Five of the twelve are two paragraphs and they ran into each other: the sections
|
|
// were spaced from one another, but nothing separated the halves of one ruling.
|
|
test("two paragraphs of a ruling are two paragraphs", () => {
|
|
assert.match(ruleFor(".ruling p + p"), /margin-top:\s*var\(--sp-\d\)/);
|
|
});
|
|
|
|
// <details> keeps its state, so without this a dealer who read the odd-chip rule an
|
|
// hour ago finds it still hanging open under the one they came back for.
|
|
test("the sheet opens shut, however it was left", () => {
|
|
const at = MAIN.indexOf("const openRulings");
|
|
assert.ok(at >= 0, "nothing owns what the sheet looks like when it opens");
|
|
const body = MAIN.slice(at, MAIN.indexOf("};", at));
|
|
assert.match(body, /\.ruling\[open\]/);
|
|
assert.match(body, /open = false/);
|
|
});
|
|
|
|
test("and that is what the button calls", () => {
|
|
assert.match(MAIN, /getElementById\("openRulingsBtn"\)\.addEventListener\("click", openRulings\)/);
|
|
});
|
|
});
|