//go:build browser package gallery import ( "context" "encoding/json" "fmt" "net/http" "testing" "time" "github.com/chromedp/chromedp" "amadan.net/rastrillo/fiesta" "amadan.net/rastrillo/fiesta/internal/harness" ) // The shell demos are the layout examples' frame documents opened full // width, and they are the shell as an app gets it. Rastrillo's two // drives on its shell demos, ported onto the layout documents. // layoutDemos is every layout document's path in the tree, in day and // English: the shell demos. func layoutDemos(t *testing.T) []string { t.Helper() var out []string for _, e := range manifest(t).Examples { if e.Document { out = append(out, frameFile(fiesta.ThemeNames()[0], "en", e.Name, e.Default)) } } if len(out) == 0 { t.Fatal("the manifest has no layout example; the drives below would read nothing") } return out } // mouseRig is a browser with a mouse, serving the tree. func mouseRig(t *testing.T) *harness.Rig { t.Helper() return harness.New(t, func(string) http.Handler { return treeHandler(t) }) } // TestTheWayBackFromAShellDemoIsATarget: the way back from a shell demo // is a link in the shell's foot, and it is a target like any other: at // least --rst-target tall, 32px with a mouse and 44px on a phone. Read // on every shell demo that has a foot, with a mouse at 1280px and a // coarse pointer at 390px; a demo whose foot link is not on screen is // counted, so a selector that stopped matching cannot pass by measuring // nothing. The link is found by where it goes, the Overview, so a foot // link left dead at "#" measures nothing and fails. func TestTheWayBackFromAShellDemoIsATarget(t *testing.T) { const read = `(() => [...document.querySelectorAll("[rst-shell-foot] a, [rst-stage-foot] a")] .filter(a => a.getAttribute("href").endsWith("/index.html") && a.checkVisibility()) .map(a => ({H: a.getBoundingClientRect().height, Floor: parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--rst-target")) * parseFloat(getComputedStyle(document.documentElement).fontSize)})))()` for _, c := range []struct { name string rig func(*testing.T) *harness.Rig w, h int64 min float64 }{ {"a mouse at 1280px", mouseRig, 1280, 800, 32}, {"a coarse pointer at 390px", func(t *testing.T) *harness.Rig { return phoneRig(t) }, 390, 844, 44}, } { rig := c.rig(t) ctx, cancel := context.WithTimeout(rig.Context(), 120*time.Second) measured := 0 for _, demo := range layoutDemos(t) { where := fmt.Sprintf("%s with %s", demo, c.name) var raw string if err := chromedp.Run(ctx, chromedp.EmulateViewport(c.w, c.h), chromedp.Navigate(rig.Origin+href(demo)), chromedp.WaitReady("body"), chromedp.Evaluate(`JSON.stringify(`+read+`)`, &raw)); err != nil { cancel() t.Fatalf("%s: %v", where, err) } var links []struct{ H, Floor float64 } if err := json.Unmarshal([]byte(raw), &links); err != nil { cancel() t.Fatalf("%s: decoding %s: %v", where, raw, err) } for _, l := range links { measured++ if l.Floor < c.min { t.Errorf("%s: --rst-target is %.0fpx, want at least %.0f", where, l.Floor, c.min) } if l.H < l.Floor-0.5 { t.Errorf("%s: the way back is %.1fpx tall, under the %.0fpx target", where, l.H, l.Floor) } } } cancel() if measured == 0 { t.Errorf("with %s no shell demo showed its way back, so this measured nothing", c.name) } t.Logf("%s: %d foot links measured", c.name, measured) } } // TestAShellDemoLaysOutAsAnAppWould: a shell demo is the shell as an // app gets it, so nothing of the gallery's own styling may move it. // Rastrillo's demos once linked gallery.css and its bar layout took // hold of the sidebar demo. Fiesta's frame documents link no gallery // stylesheet; what they do carry of the gallery's is the frame style // in their head, whose rules are meant to leave a shell exactly as it // is. Every element's box is read as served and again with every // gallery stylesheet and the frame style switched off; the two must // agree, on every layout document, the column shell included, whose // body has no wrapper for a markup rule to name and is filled by the // document mark instead. At 1280 and 900 with a mouse, either side of // the gallery bar's two-row band, and at 390 with a coarse pointer. // // Red first on the column shell, whose body sat in 16px of padding // before the document mark. Its mutation, run and reverted: dropping // "> [rst-shell-sidebar]" from the fill rule's :has() list, which gave // the sidebar demo 1rem of padding and moved every box in it once the // style was switched off, reported here per element. func TestAShellDemoLaysOutAsAnAppWould(t *testing.T) { const read = `(() => { const boxes = () => [...document.querySelectorAll("body *")].map(e => { const b = e.getBoundingClientRect(); return (e.getAttribute("id") || e.tagName.toLowerCase()) + " " + [b.left, b.top, b.width, b.height].map(n => Math.round(n)).join(","); }); const served = boxes(); const gallery = [...document.styleSheets].filter(s => (s.href && /gallery[^/]*\.css/.test(s.href)) || (!s.href && s.ownerNode && s.ownerNode.tagName === "STYLE")); gallery.forEach(s => { s.disabled = true; }); const bare = boxes(); gallery.forEach(s => { s.disabled = false; }); const diff = []; served.forEach((s, i) => { if (s !== bare[i]) diff.push(s + " (without the gallery's styling: " + bare[i] + ")"); }); return JSON.stringify({Main: !!document.querySelector("main"), Sheets: gallery.length, Diff: diff.slice(0, 5)}); })()` for _, c := range []struct { name string rig func(*testing.T) *harness.Rig w, h int64 }{ {"a mouse at 1280px", mouseRig, 1280, 800}, {"a mouse at 900px", mouseRig, 900, 800}, {"a coarse pointer at 390px", func(t *testing.T) *harness.Rig { return phoneRig(t) }, 390, 844}, } { rig := c.rig(t) ctx, cancel := context.WithTimeout(rig.Context(), 120*time.Second) for _, demo := range layoutDemos(t) { where := fmt.Sprintf("%s with %s", demo, c.name) var raw string if err := chromedp.Run(ctx, chromedp.EmulateViewport(c.w, c.h), chromedp.Navigate(rig.Origin+href(demo)), chromedp.WaitReady("body"), chromedp.Evaluate(read, &raw)); err != nil { cancel() t.Fatalf("%s: %v", where, err) } var got struct { Main bool Sheets int Diff []string } if err := json.Unmarshal([]byte(raw), &got); err != nil { cancel() t.Fatalf("%s: decoding %s: %v", where, raw, err) } if !got.Main { t.Errorf("%s: no main element; the demo is not the page this test knows", where) } if got.Sheets == 0 { t.Errorf("%s: no gallery styling was switched off, so the two readings are the same reading", where) } for _, d := range got.Diff { t.Errorf("%s: the gallery's styling moves %s", where, d) } } cancel() t.Logf("%s: %d demos read with the frame style switched off", c.name, len(layoutDemos(t))) } }