| 1 | //go:build browser |
| 2 | |
| 3 | package gallery |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "fmt" |
| 8 | "testing" |
| 9 | "time" |
| 10 | |
| 11 | "github.com/chromedp/cdproto/runtime" |
| 12 | "github.com/chromedp/chromedp" |
| 13 | "github.com/chromedp/chromedp/kb" |
| 14 | |
| 15 | "amadan.net/rastrillo/fiesta" |
| 16 | "amadan.net/rastrillo/fiesta/internal/harness" |
| 17 | ) |
| 18 | |
| 19 | // TestEveryLanguageIsReachableFromThePhoneIndex: every language in the |
| 20 | // phone index's display settings menu can be reached by Tab, with |
| 21 | // scripts, and by scrolling the way a finger would, with scripts and |
| 22 | // without, at 390px and 320px, left to right and right to left, with |
| 23 | // anchor positioning and without. The menu is at the top of the index |
| 24 | // and drops down, and the language list opens in place inside its |
| 25 | // card, so the long list is the card's to scroll. Then every link is |
| 26 | // followed once, since reaching a link is not using it. |
| 27 | // |
| 28 | // "On screen" is inside documentElement's client box, on a rig that |
| 29 | // draws its scrollbars: see requireDrawnScrollbar. |
| 30 | func TestEveryLanguageIsReachableFromThePhoneIndex(t *testing.T) { |
| 31 | rig := phoneRig(t, harness.WithScrollbars()) |
| 32 | ctx, cancel := context.WithTimeout(rig.Context(), 300*time.Second) |
| 33 | defer cancel() |
| 34 | theme := fiesta.ThemeNames()[0] |
| 35 | open := `document.getElementById("ds-prefs").open = true; document.querySelector("#ds-prefs [rst-locale]").open = true; true` |
| 36 | for _, scripts := range []bool{true, false} { |
| 37 | tab := ctx |
| 38 | if !scripts { |
| 39 | tab = noScripts(t, ctx) |
| 40 | } |
| 41 | for _, w := range []int64{390, 320} { |
| 42 | for _, locale := range []string{"en", "ar"} { |
| 43 | for _, anchored := range []bool{true, false} { |
| 44 | where := fmt.Sprintf("%s/%s index at %dpx, scripts %v, anchor positioning %v", theme, locale, w, scripts, anchored) |
| 45 | mustRun(t, tab, chromedp.EmulateViewport(w, 700), |
| 46 | chromedp.Navigate(rig.Origin+pageAt(theme, locale, "index.html")), |
| 47 | chromedp.WaitVisible(`#ds-prefs`, chromedp.ByQuery)) |
| 48 | if !scripts { |
| 49 | requireScriptsOff(t, tab, where) |
| 50 | } |
| 51 | if !anchored { |
| 52 | mustRun(t, tab, chromedp.Evaluate(withoutAnchorPositioning, nil, |
| 53 | func(p *runtime.EvaluateParams) *runtime.EvaluateParams { return p.WithAwaitPromise(true) })) |
| 54 | } |
| 55 | requireAnchorPositioning(t, tab, where, "#ds-prefs > [rst-dropdown-menu]", anchored) |
| 56 | requireCoarse(t, tab) |
| 57 | requireDrawnScrollbar(t, tab, where) |
| 58 | mustRun(t, tab, chromedp.Evaluate(`document.getElementById("ds-prefs").open = true; |
| 59 | const d = document.querySelector("#ds-prefs [rst-locale]"); d.open = true; d.querySelector("summary").focus(); true`, nil)) |
| 60 | for i, code := range Locales { |
| 61 | if scripts { |
| 62 | var got string |
| 63 | // 0.5px slack: layout at a fractional rem size lands a few |
| 64 | // hundredths of a pixel past the edge, which no eye and no |
| 65 | // finger can tell from on screen. |
| 66 | mustRun(t, tab, chromedp.KeyEvent(kb.Tab), chromedp.Evaluate(`(() => { const a = document.activeElement, r = a.getBoundingClientRect(); |
| 67 | const de = document.documentElement; |
| 68 | return r.top >= -0.5 && r.bottom <= de.clientHeight + 0.5 && r.left >= -0.5 && r.right <= de.clientWidth + 0.5 ? a.lang : "off screen: " + a.outerHTML.slice(0, 60); })()`, &got)) |
| 69 | if got != code { |
| 70 | t.Errorf("%s: tab %d is %s, want the %s link on screen", where, i+1, got, code) |
| 71 | } |
| 72 | } |
| 73 | var hit bool |
| 74 | mustRun(t, tab, chromedp.Evaluate(fmt.Sprintf(`(() => { |
| 75 | const a = document.querySelector('#ds-prefs [rst-dropdown-menu] a[lang=%q]'); |
| 76 | a.scrollIntoView({block: "nearest", inline: "nearest"}); |
| 77 | const r = a.getBoundingClientRect(); |
| 78 | return document.elementFromPoint((r.left + r.right) / 2, (r.top + r.bottom) / 2) === a; |
| 79 | })()`, code), &hit)) |
| 80 | if !hit { |
| 81 | t.Errorf("%s: scrolled into view, the %s link is not what a finger at its centre touches", where, code) |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | // Each link followed once, at 390px with scripts: a plain |
| 90 | // navigation, so the leg polls with until. |
| 91 | where := theme + "/en index at 390px, scripts on" |
| 92 | for _, code := range Locales { |
| 93 | mustRun(t, ctx, chromedp.EmulateViewport(390, 700), |
| 94 | chromedp.Navigate(rig.Origin+pageAt(theme, "en", "index.html")), |
| 95 | chromedp.WaitVisible(`#ds-prefs`, chromedp.ByQuery), |
| 96 | chromedp.Evaluate(open, nil), |
| 97 | chromedp.Evaluate(fmt.Sprintf(`document.querySelector('#ds-prefs [rst-dropdown-menu] a[lang=%q]').click(); true`, code), nil)) |
| 98 | until(t, ctx, where+", landed on "+code, fmt.Sprintf(`document.documentElement.lang === %q && location.pathname === %q`, code, pageAt(theme, code, "index.html"))) |
| 99 | } |
| 100 | |
| 101 | // With scripts off the link is nothing but its href: one activation |
| 102 | // through a real navigation. |
| 103 | offWhere := theme + "/en index at 390px, scripts off" |
| 104 | off := noScripts(t, ctx) |
| 105 | mustRun(t, off, chromedp.EmulateViewport(390, 700), |
| 106 | chromedp.Navigate(rig.Origin+pageAt(theme, "en", "index.html")), |
| 107 | chromedp.WaitVisible(`#ds-prefs`, chromedp.ByQuery)) |
| 108 | requireScriptsOff(t, off, offWhere) |
| 109 | mustRun(t, off, chromedp.Evaluate(open, nil), |
| 110 | chromedp.Evaluate(`document.querySelector('#ds-prefs [rst-dropdown-menu] a[lang="ar"]').click(); true`, nil)) |
| 111 | until(t, off, offWhere+", landed on ar", fmt.Sprintf(`document.documentElement.lang === "ar" && location.pathname === %q`, pageAt(theme, "ar", "index.html"))) |
| 112 | requireScriptsOff(t, off, offWhere+", on the ar index") |
| 113 | } |
| 114 | |