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
3package gallery
4
5import (
6 "context"
7 "encoding/json"
8 "fmt"
9 "net/http"
10 "testing"
11 "time"
12
13 "github.com/chromedp/chromedp"
14
15 "amadan.net/rastrillo/fiesta"
16 "amadan.net/rastrillo/fiesta/internal/harness"
17)
18
19// The shell demos are the layout examples' frame documents opened full
20// width, and they are the shell as an app gets it. Rastrillo's two
21// drives on its shell demos, ported onto the layout documents.
22
23// layoutDemos is every layout document's path in the tree, in day and
24// English: the shell demos.
25func layoutDemos(t *testing.T) []string {
26 t.Helper()
27 var out []string
28 for _, e := range manifest(t).Examples {
29 if e.Document {
30 out = append(out, frameFile(fiesta.ThemeNames()[0], "en", e.Name, e.Default))
31 }
32 }
33 if len(out) == 0 {
34 t.Fatal("the manifest has no layout example; the drives below would read nothing")
35 }
36 return out
37}
38
39// mouseRig is a browser with a mouse, serving the tree.
40func mouseRig(t *testing.T) *harness.Rig {
41 t.Helper()
42 return harness.New(t, func(string) http.Handler { return treeHandler(t) })
43}
44
45// TestTheWayBackFromAShellDemoIsATarget: the way back from a shell demo
46// is a link in the shell's foot, and it is a target like any other: at
47// least --rst-target tall, 32px with a mouse and 44px on a phone. Read
48// on every shell demo that has a foot, with a mouse at 1280px and a
49// coarse pointer at 390px; a demo whose foot link is not on screen is
50// counted, so a selector that stopped matching cannot pass by measuring
51// nothing. The link is found by where it goes, the Overview, so a foot
52// link left dead at "#" measures nothing and fails.
53func TestTheWayBackFromAShellDemoIsATarget(t *testing.T) {
54 const read = `(() => [...document.querySelectorAll("[rst-shell-foot] a, [rst-stage-foot] a")]
55 .filter(a => a.getAttribute("href").endsWith("/index.html") && a.checkVisibility())
56 .map(a => ({H: a.getBoundingClientRect().height,
57 Floor: parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--rst-target")) * parseFloat(getComputedStyle(document.documentElement).fontSize)})))()`
58 for _, c := range []struct {
59 name string
60 rig func(*testing.T) *harness.Rig
61 w, h int64
62 min float64
63 }{
64 {"a mouse at 1280px", mouseRig, 1280, 800, 32},
65 {"a coarse pointer at 390px", func(t *testing.T) *harness.Rig { return phoneRig(t) }, 390, 844, 44},
66 } {
67 rig := c.rig(t)
68 ctx, cancel := context.WithTimeout(rig.Context(), 120*time.Second)
69 measured := 0
70 for _, demo := range layoutDemos(t) {
71 where := fmt.Sprintf("%s with %s", demo, c.name)
72 var raw string
73 if err := chromedp.Run(ctx, chromedp.EmulateViewport(c.w, c.h),
74 chromedp.Navigate(rig.Origin+href(demo)),
75 chromedp.WaitReady("body"),
76 chromedp.Evaluate(`JSON.stringify(`+read+`)`, &raw)); err != nil {
77 cancel()
78 t.Fatalf("%s: %v", where, err)
79 }
80 var links []struct{ H, Floor float64 }
81 if err := json.Unmarshal([]byte(raw), &links); err != nil {
82 cancel()
83 t.Fatalf("%s: decoding %s: %v", where, raw, err)
84 }
85 for _, l := range links {
86 measured++
87 if l.Floor < c.min {
88 t.Errorf("%s: --rst-target is %.0fpx, want at least %.0f", where, l.Floor, c.min)
89 }
90 if l.H < l.Floor-0.5 {
91 t.Errorf("%s: the way back is %.1fpx tall, under the %.0fpx target", where, l.H, l.Floor)
92 }
93 }
94 }
95 cancel()
96 if measured == 0 {
97 t.Errorf("with %s no shell demo showed its way back, so this measured nothing", c.name)
98 }
99 t.Logf("%s: %d foot links measured", c.name, measured)
100 }
101}
102
103// TestAShellDemoLaysOutAsAnAppWould: a shell demo is the shell as an
104// app gets it, so nothing of the gallery's own styling may move it.
105// Rastrillo's demos once linked gallery.css and its bar layout took
106// hold of the sidebar demo. Fiesta's frame documents link no gallery
107// stylesheet; what they do carry of the gallery's is the frame style
108// in their head, whose rules are meant to leave a shell exactly as it
109// is. Every element's box is read as served and again with every
110// gallery stylesheet and the frame style switched off; the two must
111// agree, on every layout document, the column shell included, whose
112// body has no wrapper for a markup rule to name and is filled by the
113// document mark instead. At 1280 and 900 with a mouse, either side of
114// the gallery bar's two-row band, and at 390 with a coarse pointer.
115//
116// Red first on the column shell, whose body sat in 16px of padding
117// before the document mark. Its mutation, run and reverted: dropping
118// "> [rst-shell-sidebar]" from the fill rule's :has() list, which gave
119// the sidebar demo 1rem of padding and moved every box in it once the
120// style was switched off, reported here per element.
121func TestAShellDemoLaysOutAsAnAppWould(t *testing.T) {
122 const read = `(() => {
123 const boxes = () => [...document.querySelectorAll("body *")].map(e => {
124 const b = e.getBoundingClientRect();
125 return (e.getAttribute("id") || e.tagName.toLowerCase()) + " " + [b.left, b.top, b.width, b.height].map(n => Math.round(n)).join(",");
126 });
127 const served = boxes();
128 const gallery = [...document.styleSheets].filter(s => (s.href && /gallery[^/]*\.css/.test(s.href)) || (!s.href && s.ownerNode && s.ownerNode.tagName === "STYLE"));
129 gallery.forEach(s => { s.disabled = true; });
130 const bare = boxes();
131 gallery.forEach(s => { s.disabled = false; });
132 const diff = [];
133 served.forEach((s, i) => { if (s !== bare[i]) diff.push(s + " (without the gallery's styling: " + bare[i] + ")"); });
134 return JSON.stringify({Main: !!document.querySelector("main"), Sheets: gallery.length, Diff: diff.slice(0, 5)});
135 })()`
136 for _, c := range []struct {
137 name string
138 rig func(*testing.T) *harness.Rig
139 w, h int64
140 }{
141 {"a mouse at 1280px", mouseRig, 1280, 800},
142 {"a mouse at 900px", mouseRig, 900, 800},
143 {"a coarse pointer at 390px", func(t *testing.T) *harness.Rig { return phoneRig(t) }, 390, 844},
144 } {
145 rig := c.rig(t)
146 ctx, cancel := context.WithTimeout(rig.Context(), 120*time.Second)
147 for _, demo := range layoutDemos(t) {
148 where := fmt.Sprintf("%s with %s", demo, c.name)
149 var raw string
150 if err := chromedp.Run(ctx, chromedp.EmulateViewport(c.w, c.h),
151 chromedp.Navigate(rig.Origin+href(demo)),
152 chromedp.WaitReady("body"),
153 chromedp.Evaluate(read, &raw)); err != nil {
154 cancel()
155 t.Fatalf("%s: %v", where, err)
156 }
157 var got struct {
158 Main bool
159 Sheets int
160 Diff []string
161 }
162 if err := json.Unmarshal([]byte(raw), &got); err != nil {
163 cancel()
164 t.Fatalf("%s: decoding %s: %v", where, raw, err)
165 }
166 if !got.Main {
167 t.Errorf("%s: no main element; the demo is not the page this test knows", where)
168 }
169 if got.Sheets == 0 {
170 t.Errorf("%s: no gallery styling was switched off, so the two readings are the same reading", where)
171 }
172 for _, d := range got.Diff {
173 t.Errorf("%s: the gallery's styling moves %s", where, d)
174 }
175 }
176 cancel()
177 t.Logf("%s: %d demos read with the frame style switched off", c.name, len(layoutDemos(t)))
178 }
179}
180