Compare commits
2
Commits
4efd17d477
...
91bab4e73e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91bab4e73e | ||
|
|
f1c46b6a8e |
@@ -18,7 +18,7 @@ here has disappeared.
|
||||
|
||||
## Read this part before you fail
|
||||
|
||||
Fourteen things cost a cycle each the first time. They are here, not in a
|
||||
Fifteen things cost a cycle each the first time. They are here, not in a
|
||||
reference, because you need them *before* the failure, not after.
|
||||
|
||||
- **Time out every binding call.** A bound Go method called with wrong
|
||||
@@ -54,6 +54,16 @@ reference, because you need them *before* the failure, not after.
|
||||
`label` to `aria-labelledby` — so a new dialog that forgets to call
|
||||
the helper from `updated()` is invisible to
|
||||
`getByRole('dialog', {name})`.
|
||||
- **A name is computed on the element carrying the *role*, and Web
|
||||
Awesome puts the role in its own shadow root.** `aria-label` on a
|
||||
`<wa-slider>` or a `<wa-dialog>` host never reaches the tree. Use the
|
||||
component's own `label` (plus `styles/wa-slider-label.css.ts`, since
|
||||
a slider's is visible) or `utils/name-dialog.ts`. And in the light
|
||||
DOM, a `<label>` that is a *sibling* of its control with no `for`
|
||||
names nothing — that was 24 of the 93 controls on Settings.
|
||||
**`getFullAXTree` is how you check, and "0 unnamed" is not the whole
|
||||
answer**: a `placeholder` is an accname fallback, so a box labelled
|
||||
only by one reports clean.
|
||||
- **The a11y snapshot cannot check an accessible name on a dialog.**
|
||||
`playwright-cli snapshot` prints `- dialog [ref=…]` with no name
|
||||
whether the dialog is named by `aria-labelledby`, by `aria-label`,
|
||||
@@ -66,7 +76,7 @@ reference, because you need them *before* the failure, not after.
|
||||
`--browser=webkit` is CI-only; local work is Chromium. CI runs it
|
||||
with `if: !cancelled()` so a chromium failure does not silently
|
||||
skip it, which it did for two sessions.
|
||||
- **CI's `e2e` job is green on both engines** (54 specs each) since the
|
||||
- **CI's `e2e` job is green on both engines** (88 specs each) since the
|
||||
container got an audio device that keeps time. If playback specs
|
||||
start failing there again, check the **`The sink plays at real time`**
|
||||
step first: ALSA's `null` plugin consumes 3000 ms of audio in 2.96 ms,
|
||||
@@ -74,9 +84,9 @@ reference, because you need them *before* the failure, not after.
|
||||
reads as an app bug and cost two sessions of that suspicion.
|
||||
- **`make e2e` needs `SEED=default`.** Its specs assert on fixture
|
||||
content — unicode tracks, the fixture artists, a known playable file.
|
||||
Run against the `bulk` seed a measurement session left behind and 13
|
||||
of 36 fail, in a list that reads exactly like a regression in
|
||||
whatever you are holding. `make dev-headless SEED=default` first.
|
||||
Run against the `bulk` seed a measurement session left behind and a
|
||||
third of them fail (13 of 36, when it was measured), in a list that
|
||||
reads exactly like a regression in whatever you are holding. `make dev-headless SEED=default` first.
|
||||
- **…and the suite spends state it cannot always give back.**
|
||||
`view-lifecycle.spec.ts` **skips an autotag album** on every run, out
|
||||
of the eleven the seed has, and does not put it back — so around the
|
||||
@@ -180,7 +190,7 @@ and a CI step over every commit in a push) rejects a subject that is not
|
||||
`--no-verify` commit skips it locally and meets it in CI.
|
||||
|
||||
Two things about the e2e tier that are not obvious until they bite.
|
||||
**The 36 specs share one backend process in file order**, so a spec
|
||||
**The 88 specs share one backend process in file order**, so a spec
|
||||
that leaves the app somewhere passes alone and fails the suite — leave
|
||||
the UI as you found it, and *wait* for it rather than trusting the
|
||||
click to have finished. The queue panel's width is animated and the
|
||||
|
||||
@@ -2028,3 +2028,104 @@ Six more things worth keeping:
|
||||
palette rewrite, twice, because the component tier has no `:root` and
|
||||
renders the fallbacks. The tier that *did* catch things was a unit
|
||||
test over the palette table and a probe against the running app.
|
||||
|
||||
## A name lives where the role is, and neither the audit nor the sweep looks there
|
||||
|
||||
Plan 008 phase 3: the tail of `a11y.md`, which closes it — and with it
|
||||
all four audits from 2026-08-11.
|
||||
|
||||
The generalisation, and it is the whole of this pass: **an accessible
|
||||
name is computed on the element carrying the role, and every way we
|
||||
have of checking one looks somewhere else.** The audit read the
|
||||
*source* and credited a name that was never computed. My AX sweep read
|
||||
the *tree* and reported a weak name as no problem. A component test
|
||||
asserted the *attribute* and pinned the bug it was written to prevent.
|
||||
Three tiers, three different wrong answers, all about the same
|
||||
property.
|
||||
|
||||
Concretely, and each of these is a finding:
|
||||
|
||||
| where it was written | where the role is | computed name |
|
||||
|---|---|---|
|
||||
| `aria-label` on `<wa-slider>` | a div in its shadow root, `aria-labelledby="label"` | `""` |
|
||||
| `<label>` beside a `<select>` in `config-field` | the select | `""` |
|
||||
| `placeholder` on Explore's search input | the input | the placeholder |
|
||||
| `label` on `<wa-progress-bar>` | inner div's `aria-label` | correct |
|
||||
|
||||
The first is `wa-dialog`'s trap one component over and cost two
|
||||
sessions in 007. The fix is different, though, and the difference is
|
||||
worth keeping: `wa-progress-bar`'s `label` *is* an `aria-label` and is
|
||||
invisible, so it is just the right API; `wa-slider`'s `label` is
|
||||
**visible**, so the name comes from the library's own property and
|
||||
`styles/wa-slider-label.css.ts` hides it by part. That is preferred
|
||||
over `name-dialog.ts`'s reach into the shadow root for one reason —
|
||||
if Web Awesome renames the part, the label becomes *visible* and
|
||||
correctly named, rather than silently nameless again. Choose the
|
||||
failure you would rather have.
|
||||
|
||||
Nine more things worth keeping:
|
||||
|
||||
- **A sweep for empty names cannot see a weak one.** A `placeholder`
|
||||
is an accname fallback, so `getFullAXTree` reported the whole Explore
|
||||
view *clean* — which is why `a11y.26` survived four phases of people
|
||||
looking for exactly this class of bug. "0 unnamed" answers a
|
||||
narrower question than it reads as, which is the third time in this
|
||||
plan a *count* has done that.
|
||||
- **The count that sent Phase 1 hunting was wrong in both halves.**
|
||||
"Two unnamed native `<select>`s, one of them the page header's sort
|
||||
control on nine views": the sort control is named `Sort: ` by its
|
||||
wrapping `<label>` (`from: relatedElement`) on all nine, and the two
|
||||
unnamed roles were one `config-field` select and the *seek bar*.
|
||||
Recorded as a finding in the plan, believed for a phase, false.
|
||||
- **…and the thing it was pointing at was nine times bigger.** With
|
||||
Settings' sections expanded: **24 of 93 controls unnamed**, every
|
||||
`config-field` select and toggle and all eighteen column checkboxes.
|
||||
No finding names it, and `a11y.6` is not wrong — it says in its own
|
||||
line that it scanned every `<button>`. Same shape as phase 2's
|
||||
contrast number.
|
||||
- **A fix's own test can be pinning the bug.** `transport.test.ts`
|
||||
asserted `aria-label` on the `wa-slider` host under the title
|
||||
"carries an accessible name". It passed for six phases. *Run the
|
||||
existing tests* found it — third plan running that this is the rule
|
||||
that pays.
|
||||
- **`a11y.21`'s mechanism does not exist, and the real one is on the
|
||||
other axis.** "The 4em bars grow while the viewport does not and
|
||||
anything that no longer fits is clipped" — the middle grid row is
|
||||
`1fr` and absorbs them exactly: at 200% text on 800×600 the bars go
|
||||
64 → 128px, the panel 472 → 344px, and the footer still lands on 600.
|
||||
Nothing is clipped vertically. Horizontally the shell is 784px inside
|
||||
a 320px viewport (400% page zoom, the width 1.4.10 names) with 464px
|
||||
of it behind `overflow: hidden`. Measure the axis the finding does
|
||||
not mention.
|
||||
- **`overflow: hidden` still permits programmatic scrolling**, so
|
||||
`scrollLeft = 9999` returns a healthy 464 on the build that has the
|
||||
bug. My first spec passed against the broken build for that reason.
|
||||
A wheel gesture is the probe. Fifth entry in this plan's "the probe
|
||||
was wrong, not the code" column, and the tell was the oldest one
|
||||
there is: **it could not fail.**
|
||||
- **A synthetic `MouseEvent` does not reach a delegated handler.**
|
||||
Three probes in a row reported a queue row as never becoming active;
|
||||
`getByTestId('queue-row').dblclick()` made it active immediately.
|
||||
Delegation reads things a hand-built event does not carry.
|
||||
- **A finding can be half-closed by a phase that was not about it,
|
||||
and the half that remains is smaller than the sentence.** `a11y.34`
|
||||
reads "the sort direction is a 10px glyph *or nothing*" — Phase 1's
|
||||
`aria-sort` closed the *or nothing*, leaving one declaration. Second
|
||||
time in this plan (`a11y.11` was the first), and both times reading
|
||||
the sentence rather than the residue would have built something that
|
||||
already existed.
|
||||
- **The state a fix lands in, again, and it was three pixels.**
|
||||
`a11y.29` takes the subtitle's bottom margin away with the `<h3>`,
|
||||
which *shortens* the flex-centred title block and moves it **down**
|
||||
into the bar's clip — the hgroup had measured 67px inside a 64px bar
|
||||
since before any of this, and the descenders of "meant to bee." were
|
||||
cut. Found by reading a screenshot of the fix, which is the fifth
|
||||
regression in three plans that only a PNG has caught.
|
||||
|
||||
And one thing that went right and is worth copying: **the marker for
|
||||
`a11y.22` is a shape drawn in padding the row already had.** The track
|
||||
list's grid columns are computed from the host width, so anything in
|
||||
the flow moves every cell on the playing row and nothing else. A
|
||||
`::before` triangle in the 8px left padding costs no layout, and both
|
||||
tiers assert it is *absent* on the other rows — a marker that renders
|
||||
everywhere satisfies "the playing row has one" for free.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 008 — The last audit, and the one binding that outlived six phases
|
||||
|
||||
**Status:** active — Phases 1 and 2 shipped.
|
||||
**Status:** active — Phases 1, 2 and 3 shipped. Phase 4 is all that
|
||||
remains, and `a11y.md` is closed.
|
||||
**Branch:** main
|
||||
**Created:** 2026-08-12
|
||||
**Follows:** 007-ui-reconciliation
|
||||
@@ -64,17 +65,20 @@ fixed until it has been reproduced in the running app.
|
||||
| `15` | Major | `now-playing` is not among the four files carrying `prefers-reduced-motion`. WCAG 2.2.2: moving content over 5 s with no pause mechanism. |
|
||||
| `14` | Major | `combobox.ts` has no `aria-controls`, no `aria-activedescendant`, no option ids. |
|
||||
| `11` | Major | No `altKey` handler in `queue-panel`. The *other* half of this finding — "no keyboard path to add a track to the queue or a playlist" — was closed by Phase 5's `MenuKeyboard`. |
|
||||
| `21` | Minor | `body { height: 100vh; overflow: hidden }` unchanged. WCAG 1.4.10. |
|
||||
| `22` | Minor | `queue-panel` gained `aria-current`; `track-list` did not, and neither has a non-colour marker. |
|
||||
| `24` | Minor | No `title` on the truncating element in `track-info`, `playlist-view`, `queue-panel` or `track-list`. |
|
||||
| `25` | Minor | `<wa-progress-bar value=…>` with no label, verbatim as filed. |
|
||||
| — | new | **Two unnamed native `<select>`s**, one of them `page-header`'s sort control on nine views. Not in the audit: `a11y.6` scanned `<button>`. Found in the AX tree while reproducing `14`. Belongs with `26`. |
|
||||
| `21` | Minor | `body { height: 100vh; overflow: hidden }` unchanged. WCAG 1.4.10. **Shipped — and the stated mechanism was wrong; the failure is horizontal.** |
|
||||
| `22` | Minor | `queue-panel` gained `aria-current`; `track-list` did not, and neither has a non-colour marker. **Shipped.** |
|
||||
| `24` | Minor | No `title` on the truncating element in `track-info`, `playlist-view`, `queue-panel` or `track-list`. **Shipped.** |
|
||||
| `25` | Minor | `<wa-progress-bar value=…>` with no label, verbatim as filed. **Shipped — it was named "Progress", not unnamed.** |
|
||||
| — | ~~new~~ | ~~**Two unnamed native `<select>`s**, one of them `page-header`'s sort control on nine views.~~ **False.** The sort control is named "Sort: " by its wrapping `<label>` on all nine. The two unnamed roles were **one** `config-field` select and the **seek bar**. See Phase 3's list. |
|
||||
| — | new | **24 of 93 controls on Settings unnamed** — every `config-field` select and toggle, all eighteen column checkboxes. **Shipped, 0 of 93.** |
|
||||
| — | new | **Both `wa-slider`s have no accessible name**, which `a11y.md` files under *what is already correct*. **Shipped.** |
|
||||
| `26` | Minor | Explore's search box is named by its placeholder only — which *is* an accname fallback, so an AX sweep reports it clean. `search-bar` was already fixed. **Shipped.** |
|
||||
| `28` | ~~dropped~~ | **Measured, stays dropped.** One header *label* clips at 800×600; zero data cells do. |
|
||||
| `29` | Polish | `<h3 class="subtitle">` for type size. |
|
||||
| `30` | Polish | No skip link anywhere. |
|
||||
| `32` | Polish | `title="Remove from queue"`, not identifying the track. |
|
||||
| `34` | Polish | The 10 px sort arrow, unchanged. |
|
||||
| — | — | Colour contrast, never measured. |
|
||||
| `29` | Polish | `<h3 class="subtitle">` for type size. **Shipped.** |
|
||||
| `30` | Polish | No skip link anywhere. **Shipped.** |
|
||||
| `32` | Polish | `title="Remove from queue"`, not identifying the track. **Shipped.** |
|
||||
| `34` | Polish | The 10 px sort arrow, unchanged. **Shipped — half of it was closed by Phase 1's `aria-sort`.** |
|
||||
| — | — | Colour contrast, never measured. **Measured and fixed in Phase 2.** |
|
||||
|
||||
## Ordering principle
|
||||
|
||||
@@ -400,6 +404,106 @@ Two of them are not one-liners and should be treated as such:
|
||||
- **`22`** asks for a non-colour marker on the playing row, which is a
|
||||
visual change to the densest list in the app and moves a baseline.
|
||||
|
||||
### Phase 3 — what actually shipped
|
||||
|
||||
Six landings rather than one, ordered by risk, each reproduced in the
|
||||
running app before anything was written and each watched failing on the
|
||||
pre-fix build.
|
||||
|
||||
- **Web Awesome's two hidden roles.** `label` on both `wa-slider`s and
|
||||
on `wa-progress-bar` (`25`), plus `styles/wa-slider-label.css.ts`,
|
||||
which hides the slider's visible label by part and puts back the 8px
|
||||
margin `#slider` takes as soon as one exists.
|
||||
- **Settings' form controls.** `for`/`id` in `config-field`,
|
||||
`aria-label` on the eighteen column toggles and thirty-six column
|
||||
arrows, and the action's name on every `shortcut-capture`.
|
||||
**24 unnamed of 93 → 0.**
|
||||
- **`24` and `32`.** `title` on the four clipping surfaces, on the
|
||||
track-list *cell* rather than on what is inside it; and a queue row's
|
||||
remove button named after its own track.
|
||||
- **`29`, `30`, `34`.** A skip link, `<h3>` → `<p>`, and the sort arrow
|
||||
at the type scale's floor. Plus the state that landed in: the hgroup
|
||||
measured 67px in a 64px bar and the subtitle's descenders were
|
||||
clipped once the h3's bottom margin went with it.
|
||||
- **`22`.** A triangle in each row's own left padding, in both lists,
|
||||
and `aria-current` on the track-list row.
|
||||
- **`21`.** `overflow-x: auto` — measured, and the finding's stated
|
||||
mechanism is not the one that exists.
|
||||
|
||||
And `26`'s remaining half, found last: Explore's search box.
|
||||
|
||||
Pinned by `wa-control-names.test.ts` (4), `settings-names.test.ts`
|
||||
(11), `aria-tail.test.ts` (+5), `queue-reorder.test.ts` (+3),
|
||||
`e2e/specs/control-names.spec.ts` (3), `e2e/specs/skip-link.spec.ts`
|
||||
(4), `e2e/specs/layout-overflow.spec.ts` (+6) and
|
||||
`e2e/specs/playback.spec.ts` (+1). `make ui-test` 649 → **672**;
|
||||
`make e2e` 74 → **88**.
|
||||
|
||||
#### Where the plan was wrong — Phase 3
|
||||
|
||||
Ten things. The first four are the audit or the plan being wrong about
|
||||
where a control's name lives.
|
||||
|
||||
- **The two unnamed `<select>`s from Phase 1 were one `<select>` and a
|
||||
slider, and neither was the page header's.** `page-header`'s sort
|
||||
control computes "Sort: " from its wrapping `<label>`, on every one
|
||||
of the nine views — checked with `getFullAXTree`, `from:
|
||||
relatedElement`. The other unnamed role was the **seek bar**, which
|
||||
`a11y.md` lists under *what is already correct*. Fourth probe error
|
||||
in two passes, and the same shape as the rest: read at the wrong
|
||||
level.
|
||||
- **`aria-label` on a Web Awesome host does not name the control.**
|
||||
`wa-slider` puts `role="slider"` on a div in its own shadow root
|
||||
pointing `aria-labelledby` at an empty internal `<label>`, and that
|
||||
IDREF outranks the host's `aria-label`. Both sliders computed `""`.
|
||||
Exactly `wa-dialog`'s trap one component over, and the audit made
|
||||
exactly the same mistake in the opposite direction — it read the
|
||||
source and credited a name that was never computed.
|
||||
`volume-control` did not even have the `aria-label` it is credited
|
||||
with.
|
||||
- **`a11y.25` is not "unnamed".** `wa-progress-bar` falls back to the
|
||||
localised word *progress*, so it announced "Progress, 45%" — named
|
||||
after the widget rather than after the work. Same fix, smaller claim.
|
||||
- **Settings was full of unnamed controls and no finding says so.** 24
|
||||
of 93. `a11y.6` is not wrong: it says in its own line that it scanned
|
||||
every `<button>`. Third time this pass that a count in the audit was
|
||||
answering a narrower question than it reads as.
|
||||
- **A placeholder is an accessible name.** Explore's search box
|
||||
therefore reported *clean* in an AX sweep of all eleven views, which
|
||||
is why `a11y.26` outlived four phases of people looking for exactly
|
||||
this. A sweep for empty names cannot see a weak one.
|
||||
- **`a11y.21`'s mechanism does not exist.** "The 4em bars grow while
|
||||
the viewport does not, and anything that no longer fits is clipped
|
||||
with no scrollbar" — the middle row is `1fr` and absorbs them
|
||||
exactly. At 200% text on 800×600 the bars go 64 → 128 and the panel
|
||||
472 → 344, footer still on 600. The real failure is horizontal, which
|
||||
the finding does not mention: 784px of app in a 320px viewport, 464px
|
||||
of it unreachable.
|
||||
- **…and the obvious probe for it passes on the broken build.**
|
||||
`overflow: hidden` still permits *programmatic* scrolling, so
|
||||
`scrollLeft = 9999` returns a healthy number on the build with the
|
||||
bug. It did. The spec is a wheel gesture now.
|
||||
- **A fix's own test was pinning the bug.** `transport.test.ts`
|
||||
asserted `aria-label` on the `wa-slider` host and called it "carries
|
||||
an accessible name". Running the existing suite is what found it,
|
||||
for the third plan running.
|
||||
- **`a11y.34` was half closed by Phase 1 and nobody had noticed.** "The
|
||||
sort direction is a 10px glyph *or nothing*" — it is announced now,
|
||||
via the `aria-sort` Phase 1 added. What was left is one declaration.
|
||||
- **The queue's `aria-current` is dead in the common path.** A track
|
||||
started from the *track list* leaves the queue's `currentIndex` at
|
||||
−1, so the panel has no current row at all — which is why `22`'s
|
||||
marker looked broken the first time it was checked in the running
|
||||
app. Pre-existing, not fixed here, and the reason the e2e case plays
|
||||
from the queue.
|
||||
|
||||
And one that is about the harness rather than the audit: **a synthetic
|
||||
`MouseEvent` does not reach a delegated handler the way a real gesture
|
||||
does.** Three probes in a row reported the queue row as never becoming
|
||||
active; `page.getByTestId('queue-row').dblclick()` made it active
|
||||
immediately. Same family as everything above — the probe was wrong, not
|
||||
the code.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — `tracklist.delete`, and the operation behind it
|
||||
|
||||
@@ -130,7 +130,7 @@ meaningless against the seed's one empty playlist, so it builds ten
|
||||
It wraps every bound Go method, so "did that refetch the library" is a
|
||||
fact rather than an inference. It is not a spec and does not run in CI.
|
||||
|
||||
**The cheapest tier needs none of that.** `make ui-test` runs 480
|
||||
**The cheapest tier needs none of that.** `make ui-test` runs 672
|
||||
Vitest tests in a real Chromium in ~2 s with no Wails, no backend, no
|
||||
seeded library and no virtual display, because `frontend/wailsjs/` is a
|
||||
pure passthrough to `window.go` / `window.runtime` and
|
||||
@@ -586,6 +586,76 @@ only ever moved by an arrow key, so a row reached by a click or by Tab
|
||||
left it at 0 and `Enter` played the first track in the queue from any
|
||||
focused row.
|
||||
|
||||
**A name is computed where the role is, and that is rarely where you
|
||||
wrote it.** Four surfaces wrote a name somewhere the accessibility tree
|
||||
never looked. `wa-slider` puts `role="slider"` on a div in its own
|
||||
shadow root pointing `aria-labelledby` at an empty internal `<label>`,
|
||||
which outranks the host's `aria-label` — so both sliders computed a
|
||||
name of `""`, and `a11y.md` lists both under *what is already correct*.
|
||||
The name comes from `label` now, the library's own API, and
|
||||
`styles/wa-slider-label.css.ts` hides it: preferred over reaching into
|
||||
the shadow root the way `name-dialog.ts` must, because if Web Awesome
|
||||
renames the part the label becomes *visible and correctly named*
|
||||
rather than silently nameless. Its second rule is load-bearing —
|
||||
`#slider` takes an 8px margin the moment a label exists, which grows
|
||||
the bar 6px → 14px and moves the transport with it.
|
||||
`wa-progress-bar`'s `label` *is* an `aria-label` and is invisible, so
|
||||
there it is just the right attribute.
|
||||
|
||||
The same thing in the light DOM: `config-field` rendered a `<label>`
|
||||
as a **sibling** with no `for`, so **24 of 93 controls on Settings**
|
||||
computed an empty name. They use `for`/`id` (a fixed id, safe only
|
||||
because each field is its own shadow root) rather than `aria-label`,
|
||||
for what it buys beyond the name — the label text becomes a click
|
||||
target. And three surfaces are named but identify nothing, which is
|
||||
the same fault one step milder: three shortcut buttons announced
|
||||
themselves as "S", thirty-six column arrows as "Move up", and every
|
||||
queue row's remove button as "Remove from queue".
|
||||
|
||||
**Checking any of this needs the browser's own answer, and "0 unnamed"
|
||||
is not it.** A `placeholder` is an accname fallback, so an
|
||||
`Accessibility.getFullAXTree` sweep of all eleven views reported
|
||||
Explore's search box — the audit's own `a11y.26` — as clean. A sweep
|
||||
for *empty* names cannot see a *weak* one.
|
||||
|
||||
**The shell scrolls sideways and not down.** `body` is
|
||||
`overflow-x: auto; overflow-y: hidden`, and both halves are measured.
|
||||
Vertically there is nothing to fix: the middle grid row is `1fr` and
|
||||
absorbs the 4em bars exactly — at 200% text on an 800×600 window the
|
||||
bars go 64 → 128px, the main panel 472 → 344px, and the footer still
|
||||
lands on 600. Horizontally the shell is 784px inside a 320px viewport
|
||||
(400% page zoom, the width WCAG 1.4.10 names) and 464px of it,
|
||||
including the job indicator and the queue button, used to sit behind
|
||||
`overflow: hidden`. Keeping the vertical axis fixed is what keeps the
|
||||
transport where a desktop player's transport belongs. At every size
|
||||
this app promises, no scrollbar appears. Note that `overflow: hidden`
|
||||
still permits *programmatic* scrolling, so a probe that sets
|
||||
`scrollLeft` passes on the broken build; the spec uses a wheel gesture.
|
||||
|
||||
**The playing row is a shape, not a hue.** `track-list` and
|
||||
`queue-panel` draw a `::before` triangle in each row's own left
|
||||
padding, plus `aria-current` — before, both rows were a background tint
|
||||
and a text colour and nothing else (WCAG 1.4.1). It is in the padding
|
||||
because the track list's grid columns are computed from the host width,
|
||||
so a marker in the flow moves every cell on the playing row and nothing
|
||||
else. Both tiers assert it is **absent** on the other rows: a marker
|
||||
that renders everywhere satisfies "the playing row has one" for free.
|
||||
One thing to know before checking it — a track started from the *track
|
||||
list* leaves the queue's `currentIndex` at −1, so the panel has no
|
||||
current row at all in that flow, which reads exactly like the marker
|
||||
not working.
|
||||
|
||||
**The first thing Tab reaches is a skip link.** Two details are
|
||||
load-bearing and neither is the link's text. It is `position: absolute`
|
||||
in **both** states, because `body` is a grid with named areas and an
|
||||
in-flow extra child is auto-placed into one of them. And `<main>`
|
||||
carries `tabindex="-1"`, or the fragment moves the scroll, leaves the
|
||||
tab sequence exactly where it was, and looks like it worked. The
|
||||
subtitle beside it is a `<p>`, which is also what an `hgroup` is
|
||||
supposed to contain — and dropping the `<h3>`'s bottom margin shortened
|
||||
the flex-centred title block enough to move it down into the 4em bar's
|
||||
clip, so `.title` zeroes both margins.
|
||||
|
||||
**A selectable grid is a listbox.** The four grids that ctrl/shift-select
|
||||
(`artists-view`, `genres-view`, `cover-grid`, and the queue) are
|
||||
`role="listbox" aria-multiselectable` over `role="option"` cards, not
|
||||
|
||||
@@ -1518,6 +1518,9 @@ export class ExploreView extends ViewLifecycleMixin(LitElement) {
|
||||
<wa-icon class="search-icon" name="magnifying-glass"></wa-icon>
|
||||
<input
|
||||
type="text"
|
||||
aria-label=${this.searchMode === 'lyrics'
|
||||
? 'Search the catalog by a lyric'
|
||||
: 'Search the catalog'}
|
||||
placeholder=${placeholder}
|
||||
.value=${this.searchQuery}
|
||||
@input=${this.handleInput}
|
||||
|
||||
@@ -9,15 +9,20 @@
|
||||
* checkboxes. In each case a `<label>` sat right beside the control
|
||||
* with nothing associating the two.
|
||||
*
|
||||
* Two of the fixes here are about a name that exists and does not
|
||||
* Three of the fixes here are about a name that *exists* and does not
|
||||
* identify anything, which is `a11y.32`'s complaint one page over:
|
||||
* three shortcut buttons announced themselves as "S", and thirty-six
|
||||
* column arrows as "Move up" or "Move down".
|
||||
* three shortcut buttons announced themselves as "S", thirty-six column
|
||||
* arrows as "Move up" or "Move down", and — `a11y.26`, the audit's own
|
||||
* finding — Explore's search box by a placeholder that disappears the
|
||||
* moment anyone types into it. That last one is why the finding
|
||||
* survived four phases: a placeholder is an accname fallback, so the
|
||||
* AX sweep reported the view clean.
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import '@components/config-page/config-field';
|
||||
import '@components/config-page/shortcut-capture';
|
||||
import '@components/explore-view/explore-view';
|
||||
import { fixture, shadow } from '@test/support/render';
|
||||
|
||||
/** What the `<label>` in this shadow root actually points at. */
|
||||
@@ -114,3 +119,20 @@ describe('a shortcut button says what it binds', () => {
|
||||
.toBe('Reset Next Track to N');
|
||||
});
|
||||
});
|
||||
|
||||
describe('a search box is labelled by more than its placeholder', () => {
|
||||
it('names the catalog search, which loses its placeholder on typing', async () => {
|
||||
const el = await fixture('explore-view');
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
// `a11y.26`. A placeholder *is* an accname fallback, so this box
|
||||
// was never unnamed and the AX sweep reported the view clean —
|
||||
// which is why the finding survived four phases. It is a weak name:
|
||||
// it disappears the moment the user types, and it is the only
|
||||
// thing distinguishing catalog search from lyric search.
|
||||
const input = shadow(el, '.search-container input');
|
||||
|
||||
expect(input?.getAttribute('aria-label')).toBe('Search the catalog');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user