Let the undo pill finish its own sentence
The bar's undo is a fixed-width pill because its text changes on every tap and a control that resizes under a thumb reads as the interface twitching. On a phone that leaves the label 78px, so the label is three elements and fitUndoLabel drops the actor whole rather than letting an ellipsis land wherever it lands. That machinery was right. The labels fed to it were not. Two rules follow from "the actor is the half that goes", and six of the fifteen labels broke one. The act has to name the action on its own, because it is what survives. Four labels put a verb in the actor's slot — "dealing" + "the hand", "dealing" + "everybody in", "calling" + "the straddle off" — so dropping the lead left the pill reading "↶ the hand" after every deal of the night, and "↶ the straddle off". Neither names anything. Those are re-split: "the deal", "everybody dealt in", "the straddle called off". The act has to fit, because whatever is left is ellipsised. Measured at 390x844 and 360x640 with a thumb: "hand killed on the clock" wanted 125px of 78, "check on the clock" 99, "waiting for the blind" 103, "posting to come in" 97 — all four came out cut mid-phrase, "↶ waiting for th…". The qualifier goes into a new optional `long`, which is the whole sentence, and the verb stays on the pill. Every short form is a substring of its long form, which is what Label in Name asks of a control that has shortened its own name — and it is what lets `long` be the accessible name rather than only a title a phone has no way to show. Before this the name was whatever was visible: "↶ hand killed U", arrow and keycap included. Both are aria-hidden now. Measured after, on both phone sizes: nothing ellipsised, and every visible form contained in the name. One exception, stated in the code: a five-figure raise. "raise to $12,500" is 86px of 78 and the number is the whole of what it says, so there is no qualifier to move — the leading digits survive, the amount is on that player's seat puck, and the whole of it is on the name. Three comments carrying the old 73px figure are re-measured while I was here. 1353 tests pass, 11 of them new — 3 fail against the old labels. The sweep is clean across 428 audits; contrast unchanged. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
@@ -4195,9 +4195,9 @@ body.hasActionBar #holdemApp {
|
||||
And which part of the label shortens is decided too. It is three items rather than one
|
||||
string — the arrow, the actor, the act — so that a pill too narrow for all of it drops
|
||||
the actor whole instead of cutting the sentence off wherever the ellipsis lands. On a
|
||||
phone this label has 73px and "↶ Mike from work's fold" wants 128; cut, that reads
|
||||
phone this label has 78px and "↶ Mike from work's fold" wants 128; cut, that reads
|
||||
"↶ Mike from wor…", which names everything except the thing you would be taking back.
|
||||
See ActionBar.fitUndoLabel. */
|
||||
See ActionBar.fitUndoLabel, and undoParts for what that leaves the act having to be. */
|
||||
.actionUndoLabel {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -4216,9 +4216,13 @@ body.hasActionBar #holdemApp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The half that stays. It can still ellipsize — "raise to $1,200" is 69px of the 59 the
|
||||
label has on the narrowest phone once the arrow is drawn — and it is the only act that
|
||||
ever does. */
|
||||
/* The half that stays. It can still ellipsize, and one act does: a five-figure raise.
|
||||
"raise to $12,500" is 86px of the 78 this box has on a phone, and the number is the
|
||||
whole of what it says — there is no qualifier to move into the long form the way the
|
||||
clock rulings moved theirs. The leading digits survive, the amount is on the player's
|
||||
own seat puck, and the whole of it is on the button's name. Everything else fits:
|
||||
measured on a thumb at 390x844 and 360x640, the widest act that is not an amount is
|
||||
"forced check" at 68. */
|
||||
.actionUndoAct {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -4272,9 +4276,10 @@ body.hasActionBar #holdemApp {
|
||||
The undo pill's cap is a different class (see .actionUndoKey: this one pins itself to
|
||||
a corner and that one sits inline) and had been left out of this rule since the day it
|
||||
was written, so the one control on the bar too narrow to spare the room was the only
|
||||
one still printing a shortcut nothing could press. It is 18px and a 4px gap of a 73px
|
||||
label — enough, on its own, for "raise to $1,200" to keep its last three digits. The
|
||||
Keyboard panel still lists the shortcut for anyone who has a keyboard to use it. */
|
||||
one still printing a shortcut nothing could press. It is 18px and a 4px gap: measured
|
||||
on a thumb the act has 78px with the cap gone and 56 with it, which is the difference
|
||||
between "raise to $1,200" keeping its digits and losing them. The Keyboard panel still
|
||||
lists the shortcut for anyone who has a keyboard to use it. */
|
||||
@media (hover: none) {
|
||||
.actionKey,
|
||||
.actionUndoKey {
|
||||
|
||||
@@ -497,6 +497,12 @@ const ActionBar = {
|
||||
undo.type = "button";
|
||||
undo.className = "actionUndo";
|
||||
undo.title = `Undo ${undoLabel}`;
|
||||
// And said out loud, not only hung off a hover. The visible label is the
|
||||
// short form and every short form is a substring of this one, which is what
|
||||
// Label in Name asks: a speech user saying what they can see still hits it,
|
||||
// and a screen reader gets the qualifier the pill had no room for. Without
|
||||
// this the name was the visible text — on a phone, "↶ hand killed U".
|
||||
undo.setAttribute("aria-label", `Undo ${undoLabel}`);
|
||||
// The label goes in its own element rather than straight onto the button.
|
||||
// The pill is a fixed width — it names the last action, so its text changes
|
||||
// on every tap, and a control that resizes under the dealer's thumb reads
|
||||
@@ -505,24 +511,22 @@ const ActionBar = {
|
||||
// truncated *through* the keycap below it, which then sat squashed against
|
||||
// the cut-off text looking like a rendering fault.
|
||||
//
|
||||
// Three elements, because what it gives up matters. Measured on a phone, the
|
||||
// label has 73px and four of the five labels a hand produces want more:
|
||||
//
|
||||
// ↶ dealing the hand 104 ↶ Christopher's fold 109
|
||||
// ↶ Mike from work's fold 128 ↶ Alexandra's fold 100
|
||||
// ↶ Dee's fold 69 (the only one that fitted)
|
||||
//
|
||||
// so the pill spent most of a night reading "↶ dealing …" — a safety net
|
||||
// that would not say what it was a net for. Widening it is not available:
|
||||
// the room it would take is the room the acting name beside it needs, and
|
||||
// that is what fitActingLine is already spending. So the actor comes off
|
||||
// instead, whole, and the act stays — see fitUndoLabel.
|
||||
// Three elements, because what it gives up matters. Measured on a phone,
|
||||
// the label has 78px and every label with an actor on it wants more:
|
||||
// "↶ Mike from work's fold" 128, "↶ Christopher's fold" 109. Widening it is
|
||||
// not available — the room it would take is the room the acting name beside
|
||||
// it needs, and that is what fitActingLine is already spending. So the actor
|
||||
// comes off, whole, and the act stays; see fitUndoLabel, and undoParts for
|
||||
// what that requires of an act.
|
||||
const label = document.createElement("span");
|
||||
label.className = "actionUndoLabel";
|
||||
|
||||
const glyph = document.createElement("span");
|
||||
glyph.className = "actionUndoGlyph";
|
||||
glyph.textContent = "↶";
|
||||
// Decoration, and hidden so it stays out of the name above — "left arrow
|
||||
// over hand killed" is not what this control is called.
|
||||
glyph.setAttribute("aria-hidden", "true");
|
||||
label.appendChild(glyph);
|
||||
|
||||
const parts = this.undoStep();
|
||||
@@ -548,6 +552,7 @@ const ActionBar = {
|
||||
// floated above the bar. Here the key belongs inline, after the label.
|
||||
key.className = "actionUndoKey";
|
||||
key.textContent = this.KEY_HINTS.actionUndo;
|
||||
key.setAttribute("aria-hidden", "true");
|
||||
undo.appendChild(key);
|
||||
undo.addEventListener("click", () => this.hooks.undoLast());
|
||||
acting.appendChild(undo);
|
||||
@@ -963,26 +968,39 @@ const ActionBar = {
|
||||
this.hooks.rerender();
|
||||
},
|
||||
|
||||
// An action's name in the two halves the undo pill needs it in: who it was, and
|
||||
// what they did.
|
||||
// An action's name in the parts the undo pill needs it in.
|
||||
//
|
||||
// The pill is a fixed width — it names the last action, so its text changes on every
|
||||
// tap, and a control that resizes under a thumb reads as the interface twitching —
|
||||
// and on a phone that width leaves the label 73px. Measured across one hand, four of
|
||||
// the five labels a hand produces want more than that: "↶ dealing the hand" 104,
|
||||
// "↶ Christopher's fold" 109, "↶ Mike from work's fold" 128, "↶ Alexandra's fold"
|
||||
// 100. Only "↶ Dee's fold" fitted, at 69. So the pill spent most of a night reading
|
||||
// "↶ dealing …" — a safety net that would not say what it was a net for.
|
||||
// tap, and a control that resizes under a thumb reads as the interface twitching.
|
||||
// Measured on a phone, that leaves the label 78px, and given the halves it can drop
|
||||
// one. The actor is the half that goes, because the dealer has the seat in front of
|
||||
// them and the question a hand later is *what* came back, not who it belonged to;
|
||||
// and because "Christo…'s fold" is a worse sentence than "fold".
|
||||
//
|
||||
// Given the halves it can drop one. The actor is the half that goes, because the
|
||||
// dealer has the seat in front of them and the question a hand later is *what* came
|
||||
// back, not who it belonged to; and because "Christo…'s fold" is a worse sentence
|
||||
// than "fold". A label handed over as one string keeps working and simply has
|
||||
// nothing to drop.
|
||||
// Two rules follow, and both were being broken:
|
||||
//
|
||||
// `act` has to name the action on its own, because it is what survives. Four
|
||||
// labels put a verb in the lead instead of an actor — "dealing" + "the hand",
|
||||
// "calling" + "the straddle off" — so dropping the lead left the pill reading
|
||||
// "↶ the hand" after every deal of the night, and "↶ the straddle off", neither
|
||||
// of which names anything. Those are re-split; the ones with no actor at all
|
||||
// hand over a single string and have nothing to drop.
|
||||
//
|
||||
// `act` has to fit, because the box ellipsises whatever is left. Measured at
|
||||
// 390x844 with a thumb: "hand killed on the clock" wanted 125px of 78, "check on
|
||||
// the clock" 99, "waiting for the blind" 103, "posting to come in" 97 — all four
|
||||
// came out cut mid-phrase. The qualifier moves into `long` and the verb stays on
|
||||
// the pill.
|
||||
//
|
||||
// `long` is the whole sentence, for the title and the accessible name and for the
|
||||
// toast the bar's own Undo raises. Every short form is a substring of it — that is
|
||||
// what WCAG's Label in Name asks of a control that has shortened its own name, and
|
||||
// it is why `long` can be the accessible name rather than only a title.
|
||||
undoParts(label) {
|
||||
const lead = typeof label === "string" ? "" : (label.lead || "");
|
||||
const act = typeof label === "string" ? label : label.act;
|
||||
return { lead, act, long: lead ? `${lead} ${act}` : act };
|
||||
const spelt = typeof label === "string" ? "" : (label.long || "");
|
||||
return { lead, act, long: spelt || (lead ? `${lead} ${act}` : act) };
|
||||
},
|
||||
|
||||
renderActions() {
|
||||
@@ -1013,8 +1031,13 @@ const ActionBar = {
|
||||
expired.verb === "fold" ? `Kill ${expired.who}'s hand` : `Check for ${expired.who}`,
|
||||
"actionEnforceClock",
|
||||
() => this.commit(
|
||||
// The ruling is what the sentence is for; the pill keeps the verb.
|
||||
// "hand killed on the clock" wants 125px of the 78 the pill has and
|
||||
// came out "hand killed on the cl…"; "check on the clock" wanted 99.
|
||||
{ lead: `${expired.who}'s`,
|
||||
act: expired.verb === "fold" ? "hand killed on the clock" : "check on the clock" },
|
||||
act: expired.verb === "fold" ? "hand killed" : "check",
|
||||
long: `${expired.who}'s ${expired.verb === "fold"
|
||||
? "hand killed on the clock" : "check on the clock"}` },
|
||||
() => {
|
||||
this.stopClock();
|
||||
if (expired.verb === "fold") Betting.fold(this.state, index);
|
||||
@@ -1251,7 +1274,7 @@ const ActionBar = {
|
||||
}
|
||||
if (on > 0) {
|
||||
buttons.push(this.button("No straddle", "actionStraddle actionStraddleOff",
|
||||
() => this.commit({ lead: "calling", act: "the straddle off" },
|
||||
() => this.commit({ lead: "the straddle", act: "called off" },
|
||||
() => { this.state.straddleCount = 0; })));
|
||||
}
|
||||
return buttons;
|
||||
@@ -1339,7 +1362,7 @@ const ActionBar = {
|
||||
aside.appendChild(this.button(
|
||||
benched.length === 1 ? "Deal them in" : `Deal in all ${benched.length}`,
|
||||
"dealPromptDebtBtn",
|
||||
() => this.commit({ lead: "dealing", act: "everybody in" }, () => {
|
||||
() => this.commit({ lead: "everybody", act: "dealt in" }, () => {
|
||||
benched.forEach(index => {
|
||||
this.state.seats[index].inHand = true;
|
||||
this.state.seats[index].folded = false;
|
||||
@@ -1428,7 +1451,10 @@ const ActionBar = {
|
||||
posting ? "Don't post" : `Post ${Money.format(preview.bigBlind)}${each}`,
|
||||
"dealPromptDebtBtn",
|
||||
() => this.commit(
|
||||
{ lead: who, act: posting ? "waiting for the blind" : "posting to come in" },
|
||||
// Both of these wanted around 100px of 78 and came out mid-phrase.
|
||||
{ lead: who,
|
||||
act: posting ? "waiting" : "posting",
|
||||
long: `${who} ${posting ? "waiting for the blind" : "posting to come in"}` },
|
||||
() => { this.state.postingIn = posting ? [] : ids; })
|
||||
));
|
||||
wrap.appendChild(line);
|
||||
@@ -1447,7 +1473,10 @@ const ActionBar = {
|
||||
// browser wants the "keep my data" request to come from — see
|
||||
// requestDurable() in state.js. Harmless and instant after the first time.
|
||||
if (this.hooks.keepStorage) this.hooks.keepStorage();
|
||||
this.commit({ lead: "dealing", act: "the hand" }, () => Betting.beginHand(this.state));
|
||||
// No actor to drop, so no half of this can go missing: "dealing" was the
|
||||
// lead, and dropping a lead that is a verb left the pill reading "↶ the
|
||||
// hand" after every deal of the night.
|
||||
this.commit({ act: "the deal" }, () => Betting.beginHand(this.state));
|
||||
}));
|
||||
wrap.appendChild(row);
|
||||
|
||||
@@ -1509,7 +1538,7 @@ const ActionBar = {
|
||||
return;
|
||||
}
|
||||
const street = Betting.streetLabel(Betting.STREETS[Betting.STREETS.indexOf(this.state.street) + 1]).toLowerCase();
|
||||
this.commit({ lead: "dealing", act: `the ${street}` }, () => Betting.closeStreet(this.state));
|
||||
this.commit({ act: `the ${street}` }, () => Betting.closeStreet(this.state));
|
||||
// The cards that just came off the deck. Asked for here rather than left
|
||||
// for the dealer to remember, because the moment they are holding them is
|
||||
// the only moment they know what they are without turning the board round.
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// The pill that names what Undo would take back, and whether it manages to.
|
||||
//
|
||||
// The bar's undo is a standing control rather than a toast per action, and it is a fixed
|
||||
// width on purpose: its text changes on every tap, and a control that resizes under a
|
||||
// thumb reads as the interface twitching. Measured on a phone that leaves the label
|
||||
// 78px, which is not enough for any label with an actor on it — so the label is three
|
||||
// elements (arrow, actor, act) and `ActionBar.fitUndoLabel` drops the actor whole rather
|
||||
// than letting an ellipsis land wherever it lands.
|
||||
//
|
||||
// That machinery was right and the labels being fed to it were not. Two rules follow
|
||||
// from "the actor is the half that goes", and six of the fifteen labels broke one:
|
||||
//
|
||||
// The act has to name the action on its own. Four labels put a *verb* in the actor's
|
||||
// slot — "dealing" + "the hand", "dealing" + "everybody in", "calling" + "the straddle
|
||||
// off" — so dropping the lead left the pill reading "↶ the hand", which is what it
|
||||
// said after every deal of the night, and "↶ the straddle off". Neither names an
|
||||
// action.
|
||||
//
|
||||
// The act has to fit, because whatever is left is ellipsised. Measured at 390x844 and
|
||||
// 360x640 with a thumb: "hand killed on the clock" wanted 125px of 78, "check on the
|
||||
// clock" 99, "waiting for the blind" 103, "posting to come in" 97. All four came out
|
||||
// cut mid-phrase — "↶ waiting for th…".
|
||||
//
|
||||
// Both are fixed by writing the labels differently rather than by changing the fitting:
|
||||
// the qualifier moves into `long`, which is the whole sentence, and the verb stays on
|
||||
// the pill. Every short form is a substring of its long form, which is what Label in
|
||||
// Name asks of a control that has shortened its own name — and it is what lets `long`
|
||||
// be the accessible name rather than only a `title` nothing on a phone can reach.
|
||||
//
|
||||
// Measured after, on both phone sizes: nothing ellipsised, and every visible form
|
||||
// contained in the name. The one exception is stated below.
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { ActionBar } = require("./load");
|
||||
|
||||
const SRC = path.join(__dirname, "..", "..", "JoshHeaps.Net", "wwwroot", "js", "casino", "holdem");
|
||||
const BAR = fs.readFileSync(path.join(SRC, "actionBar.js"), "utf8");
|
||||
|
||||
// Every act the bar can put on the pill, with what the pill shows once the actor has
|
||||
// been dropped and the whole sentence it belongs to.
|
||||
//
|
||||
// This table is the rule written down. A new action has to appear here before its act is
|
||||
// allowed, which is the point: the two things that go wrong — an act that needs its lead
|
||||
// to mean anything, and an act too long for the box — are both invisible until somebody
|
||||
// reads the pill on a phone.
|
||||
const ACTS = {
|
||||
"fold": { shows: "↶ Dee's fold", long: "Dee's fold" },
|
||||
"check": { shows: "↶ Dee's check", long: "Dee's check" },
|
||||
"call": { shows: "↶ Dee's call", long: "Dee's call" },
|
||||
"all-in": { shows: "↶ Dee's all-in", long: "Dee's all-in" },
|
||||
"hand killed": { shows: "↶ hand killed", long: "Dee's hand killed on the clock" },
|
||||
"waiting": { shows: "↶ Dee waiting", long: "Dee waiting for the blind" },
|
||||
"posting": { shows: "↶ Dee posting", long: "Dee posting to come in" },
|
||||
"the deal": { shows: "↶ the deal", long: "the deal" },
|
||||
"dealt in": { shows: "↶ dealt in", long: "everybody dealt in" },
|
||||
"straddle": { shows: "↶ the straddle", long: "the straddle" },
|
||||
"re-straddle": { shows: "↶ the re-straddle", long: "the re-straddle" },
|
||||
"called off": { shows: "↶ called off", long: "the straddle called off" },
|
||||
};
|
||||
|
||||
// Acts that carry an amount, and so cannot be shortened without losing the number that
|
||||
// is the whole of what they say. "raise to $12,500" is 86px of 78 and does ellipsise —
|
||||
// accepted, because the leading digits survive, the amount is on that player's own seat
|
||||
// puck, and the whole of it is on the button's name. Below five figures they all fit:
|
||||
// "raise to $1,200" is 79.
|
||||
const AMOUNTS = ["raise to ", "bet of "];
|
||||
|
||||
// One street name per act, filled in by the caller.
|
||||
const TEMPLATED = ["the ${street}"];
|
||||
|
||||
// The longest non-amount act that fits, in characters. A proxy for the 78px measured on
|
||||
// the two phones in the sweep — "re-straddle" and "hand killed" are 11 and both fit;
|
||||
// "forced check" is 12 and 68px, the widest that was tried and cleared.
|
||||
const BUDGET = 12;
|
||||
|
||||
// Every `act:` in the bar, pulled out of the source rather than listed by hand — a new
|
||||
// one that nobody adds to ACTS above has to fail this.
|
||||
const actsInSource = () => {
|
||||
const found = new Set();
|
||||
BAR.split("\n").forEach(line => {
|
||||
let at = line.indexOf("act:");
|
||||
while (at >= 0) {
|
||||
const rest = line.slice(at + 4);
|
||||
// Quoted and back-ticked tokens on that property, minus anything that is
|
||||
// the right-hand side of a comparison — `expired.verb === "fold" ? …` picks
|
||||
// the act, it is not one.
|
||||
const tokens = rest.match(/(["`])((?:\\.|(?!\1)[^\\])*)\1/g) || [];
|
||||
tokens.forEach(raw => {
|
||||
const before = rest.slice(0, rest.indexOf(raw));
|
||||
if (/[=!]==\s*$/.test(before)) return;
|
||||
found.add(raw.slice(1, -1));
|
||||
});
|
||||
at = line.indexOf("act:", at + 4);
|
||||
}
|
||||
});
|
||||
return [...found];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("what the pill is allowed to say", () => {
|
||||
test("every act in the bar is one this file has looked at", () => {
|
||||
const known = new Set([...Object.keys(ACTS), ...TEMPLATED]);
|
||||
const stray = actsInSource().filter(a =>
|
||||
!known.has(a) && !AMOUNTS.some(prefix => a.startsWith(prefix)));
|
||||
assert.deepEqual(stray, [],
|
||||
"a new act, which has to be checked against a 78px box before it is trusted");
|
||||
});
|
||||
|
||||
test("and every act this file has looked at is still in the bar", () => {
|
||||
const inSource = new Set(actsInSource());
|
||||
const gone = Object.keys(ACTS).filter(a => !inSource.has(a));
|
||||
assert.deepEqual(gone, [], "this table describes labels the bar no longer produces");
|
||||
});
|
||||
|
||||
test("none of them needs its actor to mean anything", () => {
|
||||
// The failure this is here for: "the hand", which is what the pill read after
|
||||
// every deal, and "the straddle off". An act is what survives, so an act that
|
||||
// only parses as the tail of a phrase survives as nothing.
|
||||
Object.entries(ACTS).forEach(([act, { shows }]) => {
|
||||
assert.ok(shows.includes(act), `"${shows}" does not contain "${act}"`);
|
||||
assert.doesNotMatch(act, /^(the hand|everybody in|the straddle off)$/,
|
||||
`"${act}" is one of the acts that read as nothing on their own`);
|
||||
});
|
||||
});
|
||||
|
||||
test("and none of them is too long for the box", () => {
|
||||
Object.keys(ACTS).forEach(act => {
|
||||
assert.ok(act.length <= BUDGET,
|
||||
`"${act}" is ${act.length} characters; over ${BUDGET} it ellipsises on a phone`);
|
||||
});
|
||||
});
|
||||
|
||||
test("the amounts are the exception, and they are the only one", () => {
|
||||
const amounts = actsInSource().filter(a => AMOUNTS.some(p => a.startsWith(p)));
|
||||
assert.ok(amounts.length > 0, "the raise labels have moved and this exemption is stale");
|
||||
amounts.forEach(a => assert.ok(!(a in ACTS),
|
||||
`"${a}" is listed as both an amount and a fixed act`));
|
||||
});
|
||||
});
|
||||
|
||||
describe("the whole sentence is kept where the short one cannot go", () => {
|
||||
test("a label may spell itself out at length", () => {
|
||||
const parts = ActionBar.undoParts({
|
||||
lead: "Dee's", act: "hand killed", long: "Dee's hand killed on the clock",
|
||||
});
|
||||
assert.equal(parts.act, "hand killed", "the pill gets the short one");
|
||||
assert.equal(parts.long, "Dee's hand killed on the clock", "the name gets the whole one");
|
||||
});
|
||||
|
||||
test("and without one it is still built from the halves", () => {
|
||||
assert.equal(ActionBar.undoParts({ lead: "Dee's", act: "fold" }).long, "Dee's fold");
|
||||
assert.equal(ActionBar.undoParts({ act: "the deal" }).long, "the deal",
|
||||
"an act with no actor should not be given a leading space");
|
||||
});
|
||||
|
||||
test("a plain string still works, with nothing to drop", () => {
|
||||
const parts = ActionBar.undoParts("something older");
|
||||
assert.deepEqual(parts, { lead: "", act: "something older", long: "something older" });
|
||||
});
|
||||
|
||||
test("every short form is inside the sentence it came from", () => {
|
||||
// Label in Name: a speech user saying the words they can see has to hit the
|
||||
// control, so the accessible name cannot replace the visible text with
|
||||
// different words.
|
||||
Object.entries(ACTS).forEach(([act, { long }]) => {
|
||||
assert.ok(long.includes(act), `the name "${long}" does not contain "${act}"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("what the pill is called out loud", () => {
|
||||
test("the sentence is the accessible name, not only a hover", () => {
|
||||
// Before this the name was whatever text was visible — on a phone, "↶ hand
|
||||
// killed U" — and the whole of it lived in a `title`, which a touch screen has
|
||||
// no way to show and a screen reader is not obliged to read.
|
||||
assert.match(BAR, /undo\.setAttribute\("aria-label", `Undo \$\{undoLabel\}`\)/);
|
||||
assert.match(BAR, /undo\.title = `Undo \$\{undoLabel\}`/);
|
||||
});
|
||||
|
||||
test("and the decoration stays out of it", () => {
|
||||
const undoBlock = BAR.slice(BAR.indexOf('undo.className = "actionUndo"'),
|
||||
BAR.indexOf("acting.appendChild(undo)"));
|
||||
assert.match(undoBlock, /glyph\.setAttribute\("aria-hidden", "true"\)/,
|
||||
'the arrow is read as part of the name — "left arrow over hand killed"');
|
||||
assert.match(undoBlock, /key\.setAttribute\("aria-hidden", "true"\)/,
|
||||
"the keycap is read as part of the name");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user