Hold'em dealer: making the table and the bar fit the screen they are on #119
@@ -11,8 +11,9 @@
|
|||||||
phone the felt did not start until 225px down — a quarter of the screen spent
|
phone the felt did not start until 225px down — a quarter of the screen spent
|
||||||
before the dealer saw the table. Only two of the six are per-hand controls.
|
before the dealer saw the table. Only two of the six are per-hand controls.
|
||||||
The rest have gone where they belong rather than into a "more" menu, which
|
The rest have gone where they belong rather than into a "more" menu, which
|
||||||
would have relocated the crowding rather than removed it: Reset Table to the
|
would have relocated the crowding rather than removed it: Reset Table down to
|
||||||
Session panel beside Clear history and the save/load pair, Quick Split down
|
the sidebar with the save/load pair — in a panel called Save & reset, so the
|
||||||
|
danger is on the label rather than at the end of a scroll — Quick Split down
|
||||||
with Common rulings where the other "open something else" link already lives,
|
with Common rulings where the other "open something else" link already lives,
|
||||||
and the way back to the site up top where a back link goes. *@
|
and the way back to the site up top where a back link goes. *@
|
||||||
<div id="holdemHeaderLeft">
|
<div id="holdemHeaderLeft">
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
loaded: open on a table nobody has set up yet, shut on one already
|
loaded: open on a table nobody has set up yet, shut on one already
|
||||||
half-way through a night. *@
|
half-way through a night. *@
|
||||||
<details class="sidebarPanel" id="tableSetupPanel">
|
<details class="sidebarPanel" id="tableSetupPanel">
|
||||||
<summary>Table Setup</summary>
|
<summary><h2>Table Setup</h2></summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
<label class="fieldRow">
|
<label class="fieldRow">
|
||||||
<span>Seats at table</span>
|
<span>Seats at table</span>
|
||||||
@@ -145,11 +146,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@* These four were one panel called "Session", which is 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, which
|
||||||
|
is four kinds of thing under one label. It measured at 1206px and 103
|
||||||
|
elements, and "The table itself" was 1052px down inside it, so reaching
|
||||||
|
Reset table meant opening a panel called Session and scrolling past the
|
||||||
|
money and the log to get there.
|
||||||
|
|
||||||
|
Each panel is named for the thing a dealer came looking for, and the
|
||||||
|
subheads that used to do that job are gone, because the summary says it
|
||||||
|
now. They are 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 from the rest,
|
||||||
|
the two buttons that end it. *@
|
||||||
<details class="sidebarPanel">
|
<details class="sidebarPanel">
|
||||||
<summary>Session</summary>
|
<summary><h2>Standings</h2></summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
<p id="storageWarning" class="storageWarning" hidden></p>
|
<p id="storageWarning" class="storageWarning" hidden></p>
|
||||||
<h3 class="panelSubhead">Standings</h3>
|
|
||||||
<div class="standingsTableWrap">
|
<div class="standingsTableWrap">
|
||||||
<div id="standingsTable">
|
<div id="standingsTable">
|
||||||
<div class="standingsRow standingsHeader">
|
<div class="standingsRow standingsHeader">
|
||||||
@@ -165,14 +178,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<p id="standingsEmptyHint" class="fieldHint">Everyone who has bought in shows up here, with what they'd be owed if the night ended now.</p>
|
<p id="standingsEmptyHint" class="fieldHint">Everyone who has bought in shows up here, with what they'd be owed if the night ended now.</p>
|
||||||
|
|
||||||
<h3 class="panelSubhead">Waiting list</h3>
|
|
||||||
<form id="waitingListForm" class="waitingAdd">
|
|
||||||
<input type="text" id="waitingNameInput" maxlength="24" placeholder="Who's waiting?" autocomplete="off" />
|
|
||||||
<button type="submit" id="waitingAddBtn" class="holdemBtn holdemBtnSmall">Add</button>
|
|
||||||
</form>
|
|
||||||
<ol id="waitingList" class="waitingList"></ol>
|
|
||||||
<p id="waitingEmptyHint" class="fieldHint">Nobody waiting. Add the people standing behind the chairs, and the tool will offer them the next seat that opens.</p>
|
|
||||||
|
|
||||||
<div id="bankReconcile" class="bankReconcile" hidden>
|
<div id="bankReconcile" class="bankReconcile" hidden>
|
||||||
<div class="bankReconcileRow">
|
<div class="bankReconcileRow">
|
||||||
<span>Cash taken</span>
|
<span>Cash taken</span>
|
||||||
@@ -203,8 +208,24 @@
|
|||||||
<ul id="settleUpList" class="settleUpList"></ul>
|
<ul id="settleUpList" class="settleUpList"></ul>
|
||||||
<p id="settleUpNote" class="fieldHint"></p>
|
<p id="settleUpNote" class="fieldHint"></p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<h3 class="panelSubhead">Hands</h3>
|
<details class="sidebarPanel">
|
||||||
|
<summary><h2>Waiting list</h2></summary>
|
||||||
|
<div class="panelBody">
|
||||||
|
<form id="waitingListForm" class="waitingAdd">
|
||||||
|
<input type="text" id="waitingNameInput" maxlength="24" placeholder="Who's waiting?" autocomplete="off" />
|
||||||
|
<button type="submit" id="waitingAddBtn" class="holdemBtn holdemBtnSmall">Add</button>
|
||||||
|
</form>
|
||||||
|
<ol id="waitingList" class="waitingList"></ol>
|
||||||
|
<p id="waitingEmptyHint" class="fieldHint">Nobody waiting. Add the people standing behind the chairs, and the tool will offer them the next seat that opens.</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="sidebarPanel">
|
||||||
|
<summary><h2>Hands</h2></summary>
|
||||||
|
<div class="panelBody">
|
||||||
<div class="handHistoryFilterRow">
|
<div class="handHistoryFilterRow">
|
||||||
<input id="handHistoryFilter" type="search" class="handHistoryFilter"
|
<input id="handHistoryFilter" type="search" class="handHistoryFilter"
|
||||||
placeholder="Find a hand — name, amount or #" aria-label="Filter the hand log by player, amount or hand number" />
|
placeholder="Find a hand — name, amount or #" aria-label="Filter the hand log by player, amount or hand number" />
|
||||||
@@ -218,27 +239,11 @@
|
|||||||
<button id="exportHistoryBtn" class="holdemBtn holdemBtnSmall">Export CSV</button>
|
<button id="exportHistoryBtn" class="holdemBtn holdemBtnSmall">Export CSV</button>
|
||||||
<button id="clearHistoryBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Clear history</button>
|
<button id="clearHistoryBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Clear history</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="panelSubhead">The table itself</h3>
|
|
||||||
<p class="fieldHint">
|
|
||||||
The night lives in this browser and nowhere else. Save it to a file to carry it
|
|
||||||
to another device, or to have something to come back to if this one dies.
|
|
||||||
</p>
|
|
||||||
<div class="handHistoryActions">
|
|
||||||
<button id="saveTableBtn" class="holdemBtn holdemBtnSmall">Save table file</button>
|
|
||||||
<button id="loadTableBtn" class="holdemBtn holdemBtnSmall">Load table file</button>
|
|
||||||
<input id="loadTableInput" type="file" accept="application/json,.json" hidden />
|
|
||||||
@* Down from the header, where it sat in the same weight as Resolve Pot
|
|
||||||
— the most-pressed control in a session next to the one that throws
|
|
||||||
the night away. Here it is beside Clear history, which is the other
|
|
||||||
thing that destroys records, and behind a panel you have to open. *@
|
|
||||||
<button id="resetTableBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Reset table</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="sidebarPanel">
|
<details class="sidebarPanel">
|
||||||
<summary>Chip Legend</summary>
|
<summary><h2>Chip Legend</h2></summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
<div id="chipLegendList"></div>
|
<div id="chipLegendList"></div>
|
||||||
<button id="addChipColorBtn" class="holdemBtn holdemBtnSmall">+ Add chip color</button>
|
<button id="addChipColorBtn" class="holdemBtn holdemBtnSmall">+ Add chip color</button>
|
||||||
@@ -253,7 +258,7 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="sidebarPanel" id="keyboardPanel">
|
<details class="sidebarPanel" id="keyboardPanel">
|
||||||
<summary>Keyboard</summary>
|
<summary><h2>Keyboard</h2></summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
<dl id="shortcutLegend" class="shortcutLegend"></dl>
|
<dl id="shortcutLegend" class="shortcutLegend"></dl>
|
||||||
<p class="fieldHint">Nothing fires while you're typing in a field, or while a sheet is open over the table.</p>
|
<p class="fieldHint">Nothing fires while you're typing in a field, or while a sheet is open over the table.</p>
|
||||||
@@ -261,11 +266,37 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="sidebarPanel">
|
<details class="sidebarPanel">
|
||||||
<summary>Table Notes</summary>
|
<summary><h2>Table Notes</h2></summary>
|
||||||
<div class="panelBody">
|
<div class="panelBody">
|
||||||
<textarea id="tableNotes" rows="4" aria-label="Table notes" placeholder="Anything else worth tracking — side bets, IOUs, running totals..."></textarea>
|
<textarea id="tableNotes" rows="4" aria-label="Table notes" placeholder="Anything else worth tracking — side bets, IOUs, running totals..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@* Last, and on its own, because everything in it is about the night as a whole
|
||||||
|
rather than the hand in front of you — and because two of the three buttons
|
||||||
|
are the ones you cannot take back.
|
||||||
|
|
||||||
|
Reset table came down from the header a while ago, where it sat in the same
|
||||||
|
weight as Resolve Pot: the most-pressed control in a session beside the one
|
||||||
|
that throws the night away. It used to be buried at the bottom of Session,
|
||||||
|
1052px down, which hid it but also meant nobody could find it on purpose.
|
||||||
|
Naming the panel for both jobs puts the danger on the label, where it is
|
||||||
|
read before the panel is opened rather than met at the end of a scroll. *@
|
||||||
|
<details class="sidebarPanel">
|
||||||
|
<summary><h2>Save & reset</h2></summary>
|
||||||
|
<div class="panelBody">
|
||||||
|
<p class="fieldHint">
|
||||||
|
The night lives in this browser and nowhere else. Save it to a file to carry it
|
||||||
|
to another device, or to have something to come back to if this one dies.
|
||||||
|
</p>
|
||||||
|
<div class="handHistoryActions">
|
||||||
|
<button id="saveTableBtn" class="holdemBtn holdemBtnSmall">Save table file</button>
|
||||||
|
<button id="loadTableBtn" class="holdemBtn holdemBtnSmall">Load table file</button>
|
||||||
|
<input id="loadTableInput" type="file" accept="application/json,.json" hidden />
|
||||||
|
<button id="resetTableBtn" class="holdemBtn holdemBtnSmall holdemBtnDanger">Reset table</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2217,6 +2217,26 @@ button,
|
|||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The panel's name is a heading as well as a control.
|
||||||
|
|
||||||
|
A <summary> is announced as a button and not as a heading, so the sidebar's names —
|
||||||
|
which are the whole of its navigation — were invisible to anyone moving through the
|
||||||
|
page by heading. The four that were headings were the subheads buried inside the old
|
||||||
|
Session panel, and splitting it took those away, so without this the page would have
|
||||||
|
lost the outline rather than gained one. The spec allows heading content in a summary
|
||||||
|
and both VoiceOver and NVDA expose it.
|
||||||
|
|
||||||
|
It has to look like nothing happened, so the heading gives up everything a heading
|
||||||
|
brings with it. Inline, because a block child of a summary pushes the disclosure
|
||||||
|
triangle onto its own line. */
|
||||||
|
.sidebarPanel > summary > h2 {
|
||||||
|
display: inline;
|
||||||
|
margin: 0;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.panelBody {
|
.panelBody {
|
||||||
padding-bottom: var(--sp-4);
|
padding-bottom: var(--sp-4);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ other in the page, in the load order from `TexasHoldem.cshtml`.
|
|||||||
| `theRail.test.js` | A phone on its side: which shape the bar is in, what it leaves under the felt, how a name is shortened, how the ring is pulled back onto the table |
|
| `theRail.test.js` | A phone on its side: which shape the bar is in, what it leaves under the felt, how a name is shortened, how the ring is pulled back onto the table |
|
||||||
| `feltShape.test.js` | What shape the felt is at each phone size, resolved through the media queries in source order (see below) |
|
| `feltShape.test.js` | What shape the felt is at each phone size, resolved through the media queries in source order (see below) |
|
||||||
| `potBreakdown.test.js` | How each pot says who can win it, and the nesting property that wording rests on (see below) |
|
| `potBreakdown.test.js` | How each pot says who can win it, and the nesting property that wording rests on (see below) |
|
||||||
|
| `sidebarPanels.test.js` | What the sidebar's panels are called and what lives in each (see below) |
|
||||||
| `invariants.test.js` | 1000 randomised hands per property (see below) |
|
| `invariants.test.js` | 1000 randomised hands per property (see below) |
|
||||||
|
|
||||||
## The layout audit
|
## The layout audit
|
||||||
@@ -211,6 +212,39 @@ everything, which is how it survived long enough to be written down. Both readin
|
|||||||
silent. Prefer asserting the count you expect over comparing two things whose shapes you
|
silent. Prefer asserting the count you expect over comparing two things whose shapes you
|
||||||
have not checked.
|
have not checked.
|
||||||
|
|
||||||
|
## Testing a fault that broke nothing
|
||||||
|
|
||||||
|
`sidebarPanels.test.js` reads `TexasHoldem.cshtml` rather than any JavaScript, because
|
||||||
|
the fault it covers was not a bug. Every control worked and every id resolved. What was
|
||||||
|
wrong was which panel a thing was in and what that panel was called.
|
||||||
|
|
||||||
|
The sidebar was Table Setup, **Session**, Chip Legend, Keyboard and Table Notes, and
|
||||||
|
Session 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 word. It measured 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 to find it.
|
||||||
|
|
||||||
|
Three things are asserted, and each of them is a claim a future edit could quietly undo:
|
||||||
|
|
||||||
|
- **The names say what is inside them.** The list is checked literally, because the
|
||||||
|
failure mode is a word like "Session" that could hold anything.
|
||||||
|
- **Each name is a heading.** A `<summary>` is announced as a button, so the sidebar's
|
||||||
|
names — its whole navigation — were not reachable by heading. Splitting Session also
|
||||||
|
removed the four subheads that had been the only headings down there, so without a
|
||||||
|
heading in each summary the page would have come out of this with a worse outline than
|
||||||
|
it went in with.
|
||||||
|
- **No panel goes back to being a grab-bag.** Not a size limit — Table Setup is
|
||||||
|
legitimately long. It counts *subjects*: a panel that needs its own headings to list
|
||||||
|
its parts is holding more than one thing. Two subheads survive, and the test names
|
||||||
|
them, because Settle up is the standings at the end of the night and Stacks in chips
|
||||||
|
is the chip legend counted out — the same subject at a different moment, which is what
|
||||||
|
a subhead is for.
|
||||||
|
|
||||||
|
The panels are split on the opening tag rather than parsed, which is sound only because a
|
||||||
|
sidebar panel never contains another one; Table Setup's sub-panels carry a different
|
||||||
|
class. That assumption is worth knowing about if one ever does.
|
||||||
|
|
||||||
## Why the hand evaluator is tested the way it is
|
## Why the hand evaluator is tested the way it is
|
||||||
|
|
||||||
`handRank.js` is the only module here that can pay the wrong person without anybody
|
`handRank.js` is the only module here that can pay the wrong person without anybody
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// 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 & 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 & 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(/<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"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user