Hold the inline status to its grid placement, and every row at 1280 both ways
The inline status's geometry alone could not tell the designed grid
placement from a wrapping flex line: in these rows the name shrinks
first, so a flex line also keeps lead, name, status and action on the
first line, and a flex row in place of the grid passed the test. The
grid is the design, since a flex line drops the action or the status
item by item once they outgrow the row. So every marked row (the state,
the long name and the bare row), at 390 and 320 in both directions, is
now read for a row whose computed display is grid and a status placed
at row 1 in an auto column (1 / auto / auto / auto).
The desktop check compared only the state's row in Arabic; the long
and bare rows now are too, so all three rows in both directions are
held pixel for pixel against their unmarked twins at 1280.
Red, in a scratch worktree: the flex row (display: flex in place of the
grid placement) passed the test as it was and now fails by name in all
24 legs ("the row's display is flex", "placement ... is 2 / auto / -2 /
auto, want 1 / auto / auto / auto"); an inline status padded at every
width in right-to-left pages only was caught on the state's row before
and is now caught on all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+34
−4
check/phone_variants_browser_test.go+34 −4
Intent
diff --git a/check/phone_variants_browser_test.go b/check/phone_variants_browser_test.go| index 90db7d8..bf0375e 100644 |
| --- a/check/phone_variants_browser_test.go |
| +++ b/check/phone_variants_browser_test.go |
| @@ -187,13 +187,15 @@ type rowItem struct { |
| // rowReading is the first row on the page: its four parts, how many |
| // lines the name's link takes, the row's content box (its inline start |
| -// and end, and its top) and column gap, and the document's scroll and |
| -// client widths. |
| +// and end, and its top) and column gap, the row's computed display and |
| +// the status's computed grid placement (row start and end, column start |
| +// and end), and the document's scroll and client widths. |
| type rowReading struct { |
| Lead, Name, Status, Action rowItem |
| NameLines int |
| Start, End, Top, Gap float64 |
| RTL bool |
| + Display, Placement string |
| DocScroll, DocClient float64 |
| } |
| @@ -206,6 +208,7 @@ const rowJS = `(() => { |
| return JSON.stringify({Lead: item("[rst-row-lead]"), Name: item("[rst-row-main]"), Status: item("[rst-status]"), Action: item("[rst-row-action]"), |
| NameLines: new Set([...words.getClientRects()].map((q) => Math.round(q.top))).size, |
| Start: rtl ? right : left, End: rtl ? left : right, Top: r.top + px(cs.borderTopWidth) + px(cs.paddingTop), Gap: px(cs.columnGap), RTL: rtl, |
| + Display: cs.display, Placement: ((s) => [s.gridRowStart, s.gridRowEnd, s.gridColumnStart, s.gridColumnEnd].join(" / "))(getComputedStyle(row.querySelector(":scope > [rst-status]"))), |
| DocScroll: document.documentElement.scrollWidth, DocClient: document.documentElement.clientWidth}); |
| })()` |
| @@ -237,6 +240,27 @@ func (r rowReading) width() float64 { return max(r.End-r.Start, r.Start-r.End) } |
| // onFirstLine is whether a part starts at the top of the row's content. |
| func (r rowReading) onFirstLine(it rowItem) bool { return math.Abs(it.Top-r.Top) <= 1 } |
| +// inlinePlacement is the status's computed grid placement the inline |
| +// value asks for: row 1, and the column the grid's auto-placement picks, |
| +// the implicit one straight after the name's. |
| +const inlinePlacement = "1 / auto / auto / auto" |
| + |
| +// placedByTheGrid fails unless the row is a grid and the status is |
| +// placed in it on row 1 with an auto column. The geometry alone cannot |
| +// tell this from a wrapping flex line: in these rows the name shrinks |
| +// first, so a flex line holds every part on the first line too, and |
| +// only drops the action or the status, label by label, once they |
| +// outgrow the row. The grid placement is the design, so it is held. |
| +func placedByTheGrid(t *testing.T, leg string, r rowReading) { |
| + t.Helper() |
| + if r.Display != "grid" { |
| + t.Errorf("%s: the row's display is %s; a row with an inline status is a grid, not a wrapping flex line", leg, r.Display) |
| + } |
| + if r.Placement != inlinePlacement { |
| + t.Errorf("%s: the status's grid placement (row start / end / column start / end) is %s, want %s", leg, r.Placement, inlinePlacement) |
| + } |
| +} |
| + |
| // TestAnInlineStatusStaysOnTheNamesLine: at 390 and 320, in both |
| // directions, the list-row-action state with its status marked inline |
| // keeps all four parts on the row's first line, in source order from |
| @@ -246,7 +270,10 @@ func (r rowReading) onFirstLine(it rowItem) bool { return math.Abs(it.Top-r.Top) |
| // lead and no action the status ends at the row's content end. Without |
| // the value each of those rows puts the status under the name, as 1.0 |
| // does, which is the control that the legs above see the value's work. |
| -// At 1280 each row paints as the same row unmarked, pixel for pixel. |
| +// Every marked row is a grid with the status on row 1 in an auto |
| +// column, the placement the design asks for rather than a flex line |
| +// that happens to fit. At 1280 each row, in both directions, paints as |
| +// the same row unmarked, pixel for pixel. |
| func TestAnInlineStatusStaysOnTheNamesLine(t *testing.T) { |
| doc := stateDoc(t, "list-row-action", "A short status beside the name") |
| long := swapped(t, doc, ">Release notes, August</a>", ">Release notes for August, with every change since the spring</a>") |
| @@ -274,6 +301,7 @@ func TestAnInlineStatusStaysOnTheNamesLine(t *testing.T) { |
| name string |
| it rowItem |
| }{{"the lead", r.Lead}, {"the name", r.Name}, {"the status", r.Status}, {"the action", r.Action}} |
| + placedByTheGrid(t, leg("/"), r) |
| room := r.width() |
| for i, p := range parts { |
| if !p.it.Present { |
| @@ -297,6 +325,7 @@ func TestAnInlineStatusStaysOnTheNamesLine(t *testing.T) { |
| } |
| l := readRow(t, ctx, rig.Origin+"/long"+dir, width) |
| + placedByTheGrid(t, leg("/long"), l) |
| if l.NameLines < 2 { |
| t.Errorf("%s: the long name takes %d line; it must wrap inside its column", leg("/long"), l.NameLines) |
| } |
| @@ -315,6 +344,7 @@ func TestAnInlineStatusStaysOnTheNamesLine(t *testing.T) { |
| if b.Lead.Present || b.Action.Present { |
| t.Fatalf("%s: the scratch row still has a lead or an action", leg("/bare")) |
| } |
| + placedByTheGrid(t, leg("/bare"), b) |
| if _, end := b.span(b.Status); math.Abs(end-b.width()) > 0.5 { |
| t.Errorf("%s: with no lead and no action the status ends %.1fpx along a row whose content ends at %.1fpx", leg("/bare"), end, b.width()) |
| } |
| @@ -330,7 +360,7 @@ func TestAnInlineStatusStaysOnTheNamesLine(t *testing.T) { |
| } |
| } |
| } |
| - for _, name := range []string{"/", "/long", "/bare", "/-rtl"} { |
| + for _, name := range []string{"/", "/long", "/bare", "/-rtl", "/long-rtl", "/bare-rtl"} { |
| sameAtDesktop(t, ctx, rig.Origin, name, name+"-plain", `rst-status="inline" (`+name+`)`) |
| } |
| } |