Make forty-four pixels mean 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 66 with the text turned up. Nothing looked wrong, which is why it lasted. 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, and one field is not obviously too tall. The cost is only visible in the aggregate: Table Setup 941px to 869, the waiting list 212 to 194, every panel in the sidebar open 2,993 to 2,813. The raise pad's field was worth measuring rather than assuming, because it is not only a field: 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. It takes 10px off the bar and hands them to the table. On a Galaxy S8 twelve-handed at 20px text 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", a figure a dealer cannot read at all. Written against the element types rather than the surfaces they sit on. The surface version was written first and had already missed the waiting list's name box, two hundred lines from anything it named. This is the third time this stylesheet has been caught by content-box; the panel headers and the rulings summaries are the other two. The new test holds every field given a 44px floor to being measured the way that floor assumes. 1167 tests, 418 audits clean.
This commit is contained in:
@@ -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`);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user