Artifact 6a8a60c3078579e816657daa604efe9a6a431bdae0ab2ad5ecdacfc821785805:
- File
internal/web/fleetview_test.go
— part of check-in
[d7ff96b534]
at
2026-08-26 13:28:55
on branch trunk
— footer: shed the timing breakdown before it overlaps the links
At phone width the brand and version already retire, but the timing string alone still outran the viewport — and being flex:none it printed over the footer links rather than giving way. The per-store breakdown now retires at the same step the brand does; the total, which is what the footer is for, survives.
The collapse test pinned the whole media rule, so adding a part to a step read as a regression. It now matches the breakpoint and the parts within it, and asserts the total is never itself hidden. (user: dmestas, size: 10750) [annotate] [blame] [check-ins using]
package web import ( "bytes" "html/template" "regexp" "strings" "testing" ) // A fleet row's actions are several forms. A form is block-level, so // without display:contents they stack and every row becomes three // lines tall — which is what the fleet page looked like. func TestFleetRowActionsShareOneRow(t *testing.T) { if !strings.Contains(baseHTML, ".row-acts { display: flex") { t.Error("the actions cell is not a flex row") } if !strings.Contains(baseHTML, ".row-acts form { display: contents; }") { t.Error("the forms inside it still generate their own boxes, so the buttons will stack") } if !strings.Contains(fleetAdminSource, `<div class="row-acts">`) { t.Error("the fleet row's actions are not marked up as one") } // The page must stack, not scroll, on a phone. if !strings.Contains(baseHTML, "ul.rows > li.row { grid-template-columns: minmax(0, 1fr); }") { t.Error("rows do not collapse to one column at narrow widths") } } // The footer is fixed, so every page must reserve its height plus a // visible gap. Clearing it by a hair still reads as content running into // it, and the reserve and the bar must come from one value or they drift. func TestBodyClearsTheFixedFooter(t *testing.T) { if !strings.Contains(baseHTML, "position: fixed; bottom: 0") { t.Skip("footer is no longer fixed") } // The safe-area inset is part of the reserve AND the footer's own // padding: on an iPhone with Safari's chrome collapsed, the home // indicator overlays a bottom:0 bar, and the render time sat under // it. Elsewhere env() is 0 and both terms are inert. if !strings.Contains(baseHTML, "padding-bottom: calc(var(--footer-h) + 2rem + env(safe-area-inset-bottom))") { t.Error("body does not reserve the footer's height plus the safe-area inset") } if !strings.Contains(baseHTML, "padding: 0 0 env(safe-area-inset-bottom) 0") { t.Error("the footer does not pad above the home indicator") } if !strings.Contains(baseHTML, "viewport-fit=cover") { t.Error("without viewport-fit=cover the safe-area env() is always zero") } if !strings.Contains(baseHTML, "min-height: var(--footer-h)") { t.Error("the footer does not use the height the page reserved for it") } } // The Popover API is click-driven; using it as a tooltip gave a click // target that ALSO carried a native title, so two tooltips competed and // the cursor turned into a question mark. There is no standard hover // tooltip primitive (popover="hint" is Chrome-only), so column headers // carry a real visible label instead — readable by touch and by a // screen reader, neither of which can hover. func TestColumnHeadersDoNotUsePopoverAsATooltip(t *testing.T) { if strings.Contains(baseHTML, "popovertarget=\"tip-") { t.Error("a column header still opens a popover as a tooltip") } if strings.Contains(baseHTML, "cursor: help") { t.Error("the question-mark cursor is back") } if !strings.Contains(baseHTML, ".collabel") { t.Error("column headers have no visible label to fall back from") } // The icon alone must still have an accessible name at every width. if !strings.Contains(baseHTML, `.colhead { display: inline-flex`) { t.Error("the header label and icon are not one unit") } } // At the wide breakpoint the header labels appear and the tooltips // that carried their words retire; below it the sr-only label names // each column for a screen reader and the tooltip for a pointer. The // legend that used to restate all four above the header is gone — it // read as a duplicate of the header row itself. func TestIconColumnsAreExplainedAtEveryWidth(t *testing.T) { const labelAt = ".collabel { position: static; width: auto; height: auto; overflow: visible;" if !strings.Contains(baseHTML, labelAt) { t.Error("header labels do not appear at the wide breakpoint") } if !strings.Contains(baseHTML, `.colhead[data-tip]::after { display: none; }`) { t.Error("tooltips do not retire where the visible label already says the word") } if strings.Contains(baseHTML, "collegend") { t.Error("the legend is back; it duplicates the header row beneath it") } } // The footer is one line, so something must give on a narrow screen — // but the render time is the reason that bar earns its space. It is // therefore the part that never shrinks, and the forge name and version // collapse ahead of it. Truncating the whole left half dropped the // timing FIRST, which is exactly backwards. func TestFooterKeepsTheRenderTime(t *testing.T) { if !strings.Contains(baseHTML, ".f-time { flex: none; }") { t.Error("the render time can shrink; it must not") } if strings.Contains(baseHTML, ".foot-l { min-width: 0; overflow: hidden; text-overflow: ellipsis;") { t.Error("the whole left half truncates again, which cuts the timing off the end") } // The lower-priority parts must retire before it, at declared and // distinct widths. Matched by the breakpoint and the part rather // than by the whole rule: what matters is WHICH width sheds WHAT, // so adding another part to a step must not read as a regression. step := func(width string) string { at := strings.Index(baseHTML, "@media (max-width: "+width+") {") if at < 0 { return "" } end := strings.Index(baseHTML[at:], "}") if end < 0 { return "" } return baseHTML[at : at+end] } wide, narrow := step("34rem"), step("26rem") if wide == "" || narrow == "" { t.Fatal("the collapse order is not declared") } for _, part := range []string{".f-brand", ".f-ops"} { if !strings.Contains(wide, part) { t.Errorf("%s does not retire at the wide step", part) } } if !strings.Contains(narrow, ".f-ver") { t.Error(".f-ver does not retire at the narrow step") } // The TOTAL is what the footer is for. Only the per-store breakdown // may be shed; a rule hiding .f-time itself would take the whole // reason the bar earns its line. if strings.Contains(baseHTML, ".f-time { display: none") || strings.Contains(baseHTML, ".f-time{display:none") { t.Error("the render time itself is hidden; only .f-ops may retire") } } // Datastar morphs #fleet and #tl from the server on every change. A // widget holding its own state inside one of those regions is destroyed // mid-interaction by a repaint — an open menu vanishes, a half-typed // filter resets. This is the failure Morpheus was built to work around // in JS-state component kits; quarry avoids it structurally by keeping // every popover in page chrome, OUTSIDE the morphed region. // // That is currently true by luck of layout rather than by rule, which // is why it is asserted: the natural next feature is a per-row menu, // and it would break in a way nobody reproduces by hand. var tmplComment = regexp.MustCompile(`(?s)\{\{-? */\*.*?\*/ *-?\}\}`) func TestMorphedRegionsHoldNoStatefulWidgets(t *testing.T) { region := func(src, define string) string { i := strings.Index(src, `{{define "`+define+`"}}`) if i < 0 { t.Fatalf("no %s template", define) } rest := src[i:] if j := strings.Index(rest[10:], "{{define "); j >= 0 { rest = rest[:j+10] } return rest } for _, tc := range []struct{ name, src, define string }{ {"fleet", fleetSource, "fleet-body"}, {"timeline", timelineSource, "tl-body"}, } { // Template comments are stripped: the prose beside these rules // discusses popovers, and a substring search finds the word in // the explanation rather than in any markup. body := tmplComment.ReplaceAllString(region(tc.src, tc.define), "") for _, bad := range []string{"popover", "data-tip"} { if strings.Contains(body, bad) { t.Errorf("%s is morphed over SSE but contains %q; a repaint would destroy it mid-interaction", tc.define, bad) } } } } // The fleet templates read mirror structs whose fields exist only to be // named in the template — nothing else touches them, so the compiler // cannot notice when a template gains a field the struct lacks. That // drift is invisible until an operator loads the page and gets a // template error: {{.Health.Gaps}} against a struct without Gaps took // the whole /fleet surface down while every test stayed green. // // Executing the templates against the real structs is the check the // compiler cannot do. func TestFleetTemplatesExecuteAgainstTheirStructs(t *testing.T) { var row fleetHealthRow row.Name, row.Code = "alpha", "alpha" row.Health.Gaps, row.Health.Failures = 1, 2 row.Health.LastErr = "boom" row.Health.Stalled, row.Health.RetryAt = true, "2026-08-19T00:00:00Z" for _, tc := range []struct { name string page *template.Template def string data pageData }{ {"fleet admin", fleetAdminPage, "content", pageData{FleetHealth: []fleetHealthRow{row}, Unhealthy: 1, ErrorTotal: 3, ServerErrors: []errlogRow{{ Message: "boom", Surface: "web", Path: "/x", Count: 3, Last: "2026-08-19T10:00:00Z"}}}}, {"fleet", fleetPage, "fleet-body", pageData{Fleet: []repoInfo{{Name: "alpha"}}}}, } { t.Run(tc.name, func(t *testing.T) { var buf bytes.Buffer if err := tc.page.ExecuteTemplate(&buf, tc.def, tc.data); err != nil { t.Fatalf("template no longer matches its structs: %v", err) } }) } } // The ticket board never widens the page: columns wrap past a floor, // empty ones fold to strips, and the old sideways-scrolling grid is // gone. A regression here is the page scrolling horizontally on a // laptop, which is the thing the board was rebuilt to stop. func TestBoardNeverScrollsSideways(t *testing.T) { if strings.Contains(baseHTML, ".board { display: grid") || strings.Contains(baseHTML, "grid-auto-columns: 82vw") { t.Error("the board is a one-row grid again; it must wrap") } if !strings.Contains(baseHTML, ".board { display: flex; flex-wrap: wrap") { t.Error("the board does not wrap its columns") } if !strings.Contains(boardSource, `class="board-col{{if not .Expanded}} folded{{end}}"`) { t.Error("empty columns no longer fold to strips") } if !strings.Contains(baseHTML, ".board-col { flex: 1 1 100%; }") { t.Error("columns do not stack on a phone") } } // On the board a column has ONE width so a card is the same object in // every column; the board leaves the reading column instead. func TestBoardColumnsHaveOneWidth(t *testing.T) { if !strings.Contains(baseHTML, ".board-col { flex: 0 0 22rem") { t.Error("board columns are fluid again; cards will change width as columns open") } if !strings.Contains(baseHTML, "main.board-page { max-width: none; }") { t.Error("the board is back inside the reading column") } if !strings.Contains(boardSource, `<a class="fold" href="{{.ToggleURL}}"`) || !strings.Contains(boardSource, `<a class="unfold" href="{{.ToggleURL}}"`) { t.Error("a column must be foldable and unfoldable from its header") } }