rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3// The row's lift, the tint on menus, tiles and options, the shell
4// tail's flat summaries and the toggle-block head, from rastrillo's
5// ui/depth_rows_browser_test.go at the seed commit, on the rows
6// fixture and the depth page rig_browser_test.go serves.
7package check
8
9import (
10 "context"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/chromedp/cdproto/input"
16 "github.com/chromedp/chromedp"
17
18 "amadan.net/rastrillo/fiesta/internal/harness"
19)
20
21// The rows fixture's real ids (rows_browser_test.go): #row-a holds
22// #link-a and the kebab #kebab-a inside its <details>; #row-n in #inert
23// is the row with nothing to open; #panel-a is row A's open menu.
24const (
25 rowA, linkA, rowInert = "#row-a", "#link-a", "#row-n"
26)
27
28// TestARowLiftsOnHoverAndSinksOnPress (spec §3), with a mouse:
29// an actionable row's hover changes its background to the hover tint
30// with only inset shadows and no stacking context; pressed, or with
31// its overlay link :active, it takes the press tint; an inert row
32// changes nothing; a menu item takes the same tint; the media tile and
33// the list-row-action row do too (the depth fixture).
34func TestARowLiftsOnHoverAndSinksOnPress(t *testing.T) {
35 rig := sizingRig(t, false, rowsPages(t), harness.WithHover())
36 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
37 defer cancel()
38 mustRun(t, ctx, chromedp.EmulateViewport(1280, 844))
39 home(t, ctx, rig.Origin)
40 still(t, ctx)
41 rest := computed(t, ctx, rowA, "background-color")
42 forceState(t, ctx, rowA, "hover")
43 if got := computed(t, ctx, rowA, "background-color"); got != "rgb(245, 247, 253)" {
44 t.Errorf("hovered row background %s, want the hover tint rgb(245, 247, 253)", got)
45 }
46 if sh := computed(t, ctx, rowA, "box-shadow"); !strings.Contains(sh, "inset") || strings.Count(sh, "inset") != strings.Count(sh, "rgb") {
47 t.Errorf("hovered row box-shadow %q, want inset layers only", sh)
48 }
49 if pos, z := computed(t, ctx, rowA, "position"), computed(t, ctx, rowA, "z-index"); pos != "relative" || z != "auto" {
50 t.Errorf("hovered row position %s z-index %s; the lift must add no stacking context", pos, z)
51 }
52 forceState(t, ctx, rowA)
53 forceState(t, ctx, linkA, "active")
54 if got := computed(t, ctx, rowA, "background-color"); got != "rgb(236, 241, 252)" {
55 t.Errorf("row with its link :active has background %s, want the press tint rgb(236, 241, 252)", got)
56 }
57 forceState(t, ctx, linkA)
58 forceState(t, ctx, rowInert, "hover", "active")
59 if got := computed(t, ctx, rowInert, "background-color"); got != rest {
60 t.Errorf("an inert row changed on hover/press: %s -> %s", rest, got)
61 }
62 forceState(t, ctx, rowInert)
63 mustRun(t, ctx, chromedp.Evaluate(`document.getElementById("kebab-a").click(), true`, nil))
64 forceState(t, ctx, "#panel-a a", "hover")
65 if got := computed(t, ctx, "#panel-a a", "background-color"); got != "rgb(245, 247, 253)" {
66 t.Errorf("hovered menu item background %s, want the hover tint", got)
67 }
68 forceState(t, ctx, "#panel-a a")
69}
70
71// TestMenusTilesAndOptionsTakeTheTint: the depth fixture's menu link,
72// menu button, keyboard-active option, media tile and list-row-action
73// row take the hover tint (the option through .is-active with no
74// pointer at all), the current menu item keeps the accent tint, and a
75// dropdown nested in a menu keeps no button face in any state.
76// A tile or a row is pressed through its overlay link: the press
77// selectors read the link's :active with the container hovered, so the
78// hover is held on the container and the active forced on the link.
79func TestMenusTilesAndOptionsTakeTheTint(t *testing.T) {
80 rig := sizingRig(t, false, depthDoc(t), harness.WithHover())
81 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
82 defer cancel()
83 open(t, ctx, rig, "/", false)
84 still(t, ctx)
85 mustRun(t, ctx, chromedp.Evaluate(`document.getElementById("dd").open = true, true`, nil))
86 for _, c := range []struct{ sel, press string }{{"#menu-a", "#menu-a"}, {"#menu-btn", "#menu-btn"}, {"#tile", "#tile-link"}, {"#row-c", "#link-c"}} {
87 forceState(t, ctx, c.sel, "hover")
88 if got := computed(t, ctx, c.sel, "background-color"); got != "rgb(245, 247, 253)" {
89 t.Errorf("%s hovered is %s, want the hover tint", c.sel, got)
90 }
91 if c.press == c.sel {
92 forceState(t, ctx, c.sel, "hover", "active")
93 } else {
94 forceState(t, ctx, c.press, "active")
95 }
96 if got := computed(t, ctx, c.sel, "background-color"); got != "rgb(236, 241, 252)" {
97 t.Errorf("%s hovered and pressed is %s, want the press tint", c.sel, got)
98 }
99 forceState(t, ctx, c.sel)
100 forceState(t, ctx, c.press)
101 }
102 for _, sel := range []string{"#menu-a", "#menu-btn"} {
103 forceState(t, ctx, sel, "active")
104 if got := computed(t, ctx, sel, "background-color"); got != "rgb(236, 241, 252)" {
105 t.Errorf("%s tapped (active, no hover) is %s, want the tap tint", sel, got)
106 }
107 forceState(t, ctx, sel)
108 }
109 if got := computed(t, ctx, "#opt-active", "background-color"); got != "rgb(245, 247, 253)" {
110 t.Errorf("the keyboard-active option is %s, want the hover tint with no pointer", got)
111 }
112 if got := computed(t, ctx, "#menu-cur", "background-color"); got != "rgb(235, 241, 254)" {
113 t.Errorf("the current menu item is %s, want --rst-accent-soft", got)
114 }
115 if bg, bw, sh := computed(t, ctx, "#dd-sum", "background-color"), computed(t, ctx, "#dd-sum", "border-top-width"), computed(t, ctx, "#dd-sum", "box-shadow"); bg != "rgb(255, 255, 255)" || bw != "0px" || !strings.HasPrefix(sh, "rgb(208, 212, 220) 0px 0px 0px 1px") {
116 t.Errorf("the list-bar summary is %s, border-width %s, shadow %q; want the summary face, no border, and the edge as a 1px ring", bg, bw, sh)
117 }
118 // Day rounds the face through --rst-summary-radius; a flat theme
119 // keeps 0, and the matrix's focused #dd-sum cell holds that side.
120 if got := computed(t, ctx, "#dd-sum", "border-top-left-radius"); got != "4px" {
121 t.Errorf("the list-bar summary's corner is %s, want day's --rst-radius-sm (4px)", got)
122 }
123 for _, states := range [][]string{{}, {"hover"}, {"hover", "active"}, {"active"}} {
124 forceState(t, ctx, "#dd-nested-sum", states...)
125 if bg, bw, sh := computed(t, ctx, "#dd-nested-sum", "background-color"), computed(t, ctx, "#dd-nested-sum", "border-top-width"), computed(t, ctx, "#dd-nested-sum", "box-shadow"); bg != "rgba(0, 0, 0, 0)" || bw != "0px" || sh != "none" {
126 t.Errorf("a dropdown nested in a menu, state %v: %s, border %s, shadow %q; it is a row of the menu, not a button", states, bg, bw, sh)
127 }
128 forceState(t, ctx, "#dd-nested-sum")
129 }
130 forceState(t, ctx, "#lra", "hover")
131 if got := computed(t, ctx, "#lra", "background-color"); got != "rgb(245, 247, 253)" {
132 t.Errorf("a hovered list-row-action row is %s, want --rst-row-tint-hover", got)
133 }
134 forceState(t, ctx, "#lra")
135 for _, sel := range []string{"#link-a", "#lra-link", "#tile-link", "#dd-sum", "#menu-a", "#menu-btn", "#opt-a", "#sw"} {
136 if got := computed(t, ctx, sel, "-webkit-tap-highlight-color"); got != "rgba(0, 0, 0, 0)" {
137 t.Errorf("%s tap highlight is %s, want transparent", sel, got)
138 }
139 }
140}
141
142// TestATouchedRowShowsThePressNotAHover: on a coarse pointer there is
143// no hover styling (the hover rules sit in (hover: hover)); a row whose
144// link is active takes the press tint, and the kebab's tap highlight is
145// off.
146func TestATouchedRowShowsThePressNotAHover(t *testing.T) {
147 rig := sizingRig(t, true, rowsPages(t))
148 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
149 defer cancel()
150 mustRun(t, ctx, chromedp.EmulateViewport(390, 844))
151 home(t, ctx, rig.Origin)
152 requirePointer(t, ctx, true)
153 still(t, ctx)
154 rest := computed(t, ctx, rowA, "background-color")
155 forceState(t, ctx, rowA, "hover")
156 if got := computed(t, ctx, rowA, "background-color"); got != rest {
157 t.Errorf("a coarse pointer's sticky hover tinted the row %s", got)
158 }
159 forceState(t, ctx, rowA)
160 forceState(t, ctx, linkA, "active")
161 if got := computed(t, ctx, rowA, "background-color"); got != "rgb(236, 241, 252)" {
162 t.Errorf("touched row is %s, want the press tint", got)
163 }
164 forceState(t, ctx, linkA)
165 if got := computed(t, ctx, "#kebab-a", "-webkit-tap-highlight-color"); got != "rgba(0, 0, 0, 0)" {
166 t.Errorf("kebab tap highlight %s, want transparent", got)
167 }
168}
169
170// TestARowsLiftNeverMovesItsOpenMenu (review focus 1): a row menu
171// opened on a lifted row stays where it opened while the lift eases
172// away. The lift is asserted first, so this fails before the lift
173// exists and is not a check of behaviour that already held. Not
174// frozen: the easing is the thing watched, so only the inspector is
175// enabled.
176func TestARowsLiftNeverMovesItsOpenMenu(t *testing.T) {
177 rig := sizingRig(t, false, rowsPages(t), harness.WithHover())
178 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
179 defer cancel()
180 mustRun(t, ctx, chromedp.EmulateViewport(1280, 844))
181 home(t, ctx, rig.Origin)
182 inspect(t, ctx)
183 forceState(t, ctx, rowA, "hover")
184 time.Sleep(300 * time.Millisecond)
185 if got := computed(t, ctx, rowA, "background-color"); got != "rgb(245, 247, 253)" {
186 t.Fatalf("row A hovered is %s; the lift this test guards is missing", got)
187 }
188 mustRun(t, ctx, chromedp.Evaluate(`document.getElementById("kebab-a").click(), true`, nil))
189 var before, after struct{ X, Y float64 }
190 at(t, ctx, `(() => { const r = document.getElementById("panel-a").getBoundingClientRect(); return JSON.stringify({X: r.left, Y: r.top}); })()`, &before)
191 forceState(t, ctx, rowA)
192 time.Sleep(300 * time.Millisecond)
193 at(t, ctx, `(() => { const r = document.getElementById("panel-a").getBoundingClientRect(); return JSON.stringify({X: r.left, Y: r.top}); })()`, &after)
194 if before != after {
195 t.Errorf("the open panel moved from %+v to %+v while the row's lift eased away", before, after)
196 }
197}
198
199// shellTailBody is a topbar in both spellings with its phone card open:
200// the tail holds the nav, the account menu and the locale and scheme
201// menus, as layouts/topbar.html renders them, and the page holds a
202// locale menu of its own, which is the control (a dropdown standing
203// alone keeps the face).
204// markup-spelling: old-spelling begin — the shell tail is drawn in both spellings, so the face exclusion is held in each.
205const shellTailBody = `<div rst-shell-topbar><header rst-shell-bar><a rst-shell-brand href="/">Home</a>
206<details rst-shell-menu name="rst-shell-menu" open><summary>Menu</summary></details>
207<div rst-shell-tail><nav rst-shell-nav><a href="#o">Overview</a></nav>
208<details rst-dropdown rst-shell-account name="rst-menus"><summary>Account</summary><div rst-dropdown-menu><a href="#s">Sign out</a></div></details>
209<details rst-dropdown rst-locale name="rst-menus"><summary id="tail-loc">Language</summary><div rst-dropdown-menu><form><button type="button" aria-current="true">English</button></form></div></details>
210<details rst-dropdown rst-scheme name="rst-menus"><summary id="tail-sch">Theme</summary><div rst-dropdown-menu><form><button type="button">Dark</button></form></div></details>
211</div></header>
212<main rst-page id="main"><details rst-dropdown rst-locale name="rst-menus"><summary id="page-loc">Language</summary><div rst-dropdown-menu><form><button type="button">English</button></form></div></details></main></div>
213<div class="rst-shell-topbar"><header class="rst-shell__bar">
214<details class="rst-shell__menu" name="rst-shell-menu-c" open><summary>Menu</summary></details>
215<div class="rst-shell__tail"><details class="rst-dropdown rst-scheme" name="rst-menus"><summary id="tail-sch-c">Theme</summary><div class="rst-dropdown__menu"><form><button type="button">Dark</button></form></div></details></div>
216</header></div>`
217
218// markup-spelling: old-spelling end
219
220// TestTheShellTailsMenusWearNoFace: in day, the
221// locale and scheme summaries in a shell's tail stay flat, as their
222// neighbours do, in the desktop bar at 1280 and as rows of the phone
223// card at 390, in every state; the same menu on the page wears the
224// summary face, so the check can see a face when there is one. Each
225// width has its own rig, opened at that width: a second
226// EmulateViewport loses the mouse (TestEveryNavSurfaceHasItsStates).
227func TestTheShellTailsMenusWearNoFace(t *testing.T) {
228 for _, width := range []int64{1280, 390} {
229 rig := sizingRig(t, false, map[string]string{"/": sizingDoc("tail", shellTailBody)}, harness.WithHover())
230 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
231 mustRun(t, ctx, chromedp.EmulateViewport(width, 844), chromedp.Navigate(rig.Origin+"/"), chromedp.WaitVisible("#page-loc", chromedp.ByQuery))
232 still(t, ctx)
233 var hov bool
234 at(t, ctx, `JSON.stringify(matchMedia("(hover: hover)").matches)`, &hov)
235 if !hov {
236 t.Fatalf("%d: (hover: hover) is false; the face's hover rules cannot be read here", width)
237 }
238 if bg := computed(t, ctx, "#page-loc", "background-color"); bg != "rgb(255, 255, 255)" {
239 t.Errorf("%d: CONTROL: the page's locale summary is %s, want the summary face; the check below cannot see a face", width, bg)
240 }
241 for _, sel := range []string{"#tail-loc", "#tail-sch", "#tail-sch-c"} {
242 for _, states := range [][]string{{}, {"hover"}, {"hover", "active"}, {"active"}} {
243 forceState(t, ctx, sel, states...)
244 if bg, sh := computed(t, ctx, sel, "background-color"), computed(t, ctx, sel, "box-shadow"); bg != "rgba(0, 0, 0, 0)" || sh != "none" {
245 t.Errorf("%d: shell-tail summary %s %v is %s with box-shadow %q; want no face (rgba(0, 0, 0, 0), none)", width, sel, states, bg, sh)
246 }
247 forceState(t, ctx, sel)
248 }
249 }
250 cancel()
251 }
252}
253
254// TestATBlockHeadTapsWithoutAFlash: a tap on the head's title (not the
255// track) toggles the block, which is native <label> behaviour and
256// already held; the new claims are that the head is the press surface
257// (no browser tap highlight) and that the pressed head takes no tint
258// of its own, because its switch is the feedback. The highlight check
259// passed before the head had a hover rule: the tap-highlight rule
260// already named the head; the background check is what fails if the
261// head's hover tint leaves the hover query.
262func TestATBlockHeadTapsWithoutAFlash(t *testing.T) {
263 body := `<div rst-page><div rst-tblock id="tb"><label rst-tblock-head id="head"><input type="checkbox"><span rst-switch-track aria-hidden="true"></span><span rst-tblock-title id="title">Block</span></label><div rst-tblock-body>body</div></div></div>`
264 rig := sizingRig(t, true, map[string]string{"/": sizingDoc("tblock", body)})
265 ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
266 defer cancel()
267 mustRun(t, ctx, chromedp.EmulateViewport(390, 844), chromedp.Navigate(rig.Origin+"/"), chromedp.WaitVisible("#head", chromedp.ByQuery))
268 requirePointer(t, ctx, true)
269 x, y, w, h := rect(t, ctx, "#title")
270 mustRun(t, ctx, input.DispatchTouchEvent(input.TouchStart, []*input.TouchPoint{{X: x + w/2, Y: y + h/2}}), input.DispatchTouchEvent(input.TouchEnd, nil))
271 var checked bool
272 mustRun(t, ctx, chromedp.Evaluate(`document.querySelector("#head input").checked`, &checked))
273 if !checked {
274 t.Fatal("a tap on the title did not toggle the block")
275 }
276 if got := computed(t, ctx, "#head", "-webkit-tap-highlight-color"); got != "rgba(0, 0, 0, 0)" {
277 t.Errorf("head tap highlight %s, want transparent", got)
278 }
279 // A tap leaves the head :hover on touch, in Chromium and WebKit, so a
280 // hover tint outside (hover: hover) stuck on the tapped head.
281 if got := computed(t, ctx, "#head", "background-color"); got != "rgba(0, 0, 0, 0)" {
282 t.Errorf("head background after a tap %s, want transparent: the hover tint stuck", got)
283 }
284}
285