Hold'em: every seat on the ring is the same seat, and the ring is measured before it is believed #122

Open
jheaps wants to merge 27 commits from polish/holdem-seats-one-size into master
2 changed files with 148 additions and 0 deletions
Showing only changes of commit de4331de72 - Show all commits
@@ -207,6 +207,43 @@ a {
touch-action: manipulation; touch-action: manipulation;
} }
/* A field is as tall as it says it is.
*
* Form controls are content-box by default, and this stylesheet has been caught by that
* twice already — the sidebar's panel headers and the rulings sheet's summaries both
* carry a note about it. It was doing the same thing to the fields, and nobody had
* measured them: the touch rule near the bottom of this file asks for `min-height: 44px`
* on every input and select in a panel, 44 was being read as the *content* height, and
* 8px of padding top and bottom plus a 1px border each side went on the outside of it.
* Measured on an iPhone, all fifteen settings fields in the sidebar stood at 62px — 41%
* over the number the rule that sizes them is written in terms of.
*
* Nothing looked wrong, which is why it lasted. 62px clears a 44px floor, so the sweep
* had nothing to say, and one field is not obviously too tall. Fifteen of them are 180px
* of scroll on a page whose whole argument is that a dealer is holding a phone in one
* hand: Table Setup 941 to 869, the waiting list 212 to 194, and every panel in the
* sidebar open 2,993 to 2,813.
*
* The raise pad's field is the one worth measuring rather than assuming, because it is
* not only a field: its height is the action bar's height, the action bar's height is
* published into --feltRoomPx, and the felt is laid out from what is left. Correcting it
* takes 10px off the bar and hands them to the table. Measured on a Galaxy S8
* twelve-handed at 20px text, which is where that matters most: the bar goes 248px to
* 238, and seat 7's stack comes out from behind it — with the taller field the sweep
* reported "$1,100 is 7/7 under div#actionBar", which is a figure a dealer cannot read
* at all.
*
* Said about every field rather than about the surfaces they sit on, because a list of
* surfaces is a list that goes stale. Written that way first, it already missed one: the
* waiting list's name box asks for the same 44px two hundred lines from anything the
* list named. The rule is identical either way and there is one fewer thing to keep up
* to date. */
input,
select,
textarea {
box-sizing: border-box;
}
/* The grey rectangle goes from every control, fields included — a tapped input /* The grey rectangle goes from every control, fields included — a tapped input
already shows a caret and a focus ring, which say the same thing more quietly. */ already shows a caret and a focus ring, which say the same thing more quietly. */
.holdemBtn, .holdemBtn,
+111
View File
@@ -0,0 +1,111 @@
// Forty-four pixels, meaning forty-four pixels.
//
// Every input and select a dealer touches has a rule asking for `min-height: 44px`, and
// not one of them was 44px tall. Form controls are content-box by default, so 44 was
// being read as the height of the text inside and 8px of padding top and bottom plus a
// 1px border each side went on the outside of it. Measured on an iPhone: all fifteen
// settings fields in the sidebar stood at 62px, and the raise pad's amount box at 66px
// with the text turned up.
//
// Nothing looked wrong, which is why it lasted eleven rounds. 62px clears a 44px floor,
// so the sweep — which asks whether a target is big enough, never whether it is the size
// it was asked to be — had nothing to say about it. The cost is only visible in the
// aggregate: fifteen fields at 18px over is 180px of sidebar, and the raise field's 10px
// is 10px of felt, because the action bar's height is published into --feltRoomPx and
// the table is laid out from what is left.
//
// This is the third time this stylesheet has been caught by content-box — the sidebar's
// panel headers and the rulings sheet's summaries are the other two, and both carry a
// note saying so. The note is in the stylesheet; the list of what it covers is here.
const test = require("node:test");
const { describe } = require("node:test");
const assert = require("node:assert");
const fs = require("fs");
const path = require("path");
const CSS = fs.readFileSync(path.join(
__dirname, "..", "..", "JoshHeaps.Net", "wwwroot", "css", "casino", "holdem.css"), "utf8");
// The rule, the selectors it is written against, and the note above it saying why.
const boxSizing = () => {
const note = CSS.indexOf("/* A field is as tall as it says it is.");
assert.ok(note >= 0, "nothing in the stylesheet asks a field to be the height it states");
const at = CSS.indexOf("box-sizing: border-box;", note);
assert.ok(at >= 0);
return CSS.slice(note, CSS.indexOf("}", at));
};
// Every selector anywhere in the stylesheet that gives a typing field a 44px floor,
// wherever the media query that holds it happens to live. Checkboxes and colour swatches
// are left out: they are sized by an explicit width and height rather than by a floor
// their padding can push past, which is the whole of the bug this guards.
const touchSized = () => {
const found = [];
let from = 0;
for (;;) {
const hit = CSS.indexOf("min-height: 44px", from);
if (hit === -1) break;
from = hit + 1;
const opens = CSS.lastIndexOf("{", hit);
const prior = Math.max(CSS.lastIndexOf("}", opens), CSS.lastIndexOf("{", opens - 1),
CSS.lastIndexOf("*/", opens));
CSS.slice(prior + 1, opens).split(",")
.map(line => line.trim())
.filter(Boolean)
.forEach(selector => found.push(selector));
}
return found.filter(selector =>
/\b(input|select|textarea)\b|\.raiseInput/.test(selector) &&
!/\[type="(checkbox|color)"\]/.test(selector));
};
// ---------------------------------------------------------------------------
describe("a field is the height it says it is", () => {
const rule = boxSizing();
// Every field, not a list of the surfaces fields sit on. The list version of this
// rule was written first and had already missed one — see the note.
test("said about the elements, so nothing can sit outside the list", () => {
assert.match(rule, /(^|\n)input,\s*\n\s*select,\s*\n\s*textarea \{/);
});
// The one field whose height is not only its own: it sets the action bar's height,
// the bar's height is published into --feltRoomPx, and the felt is laid out from
// what is left. Taking 10px off the bar gave them to the table.
test("the felt's stake in it is written down", () => {
assert.match(rule, /--feltRoomPx/);
assert.match(rule, /248px to[\s*]*238/);
});
test("and so is the measurement that found it", () => {
assert.match(rule, /62px/);
assert.match(rule, /fifteen settings fields/);
});
// Two other rules in this file already carry a content-box note; this is the third
// place it has bitten, and the note is what stops it being a fourth.
test("and what it is about, so the next one is recognised", () => {
assert.match(rule, /content-box/);
assert.match(rule, /padding/);
});
});
describe("nothing asks for a height it will not get", () => {
// A durable guard rather than a snapshot. If a later round gives a field a 44px
// floor, that floor is only the height the author meant while this rule reaches the
// field — and nothing else in the suite would notice if it stopped.
test("every field given a touch height is measured the way that height assumes", () => {
const fields = touchSized();
assert.ok(fields.length >= 4, `only ${fields.length} field rules found — the scrape has drifted`);
// The rule names the element types themselves, so the only way a field escapes
// is a later round narrowing it back to a list of places.
const rule = boxSizing();
fields.forEach(selector => {
const kind = (selector.match(/\b(input|select|textarea)\b/) || [])[1]
|| (/\.raiseInput/.test(selector) ? "input" : null);
assert.ok(kind && new RegExp(`(^|\\n|,\\s*)${kind}\\b`).test(rule),
`\`${selector}\` is given a touch height that box-sizing no longer reaches`);
});
});
});