| 1 | //go:build browser |
| 2 | |
| 3 | package gallery |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "net/http" |
| 8 | "testing" |
| 9 | "time" |
| 10 | |
| 11 | "github.com/chromedp/cdproto/emulation" |
| 12 | "github.com/chromedp/chromedp" |
| 13 | |
| 14 | "amadan.net/rastrillo/fiesta/internal/gallery/galleryrig" |
| 15 | "amadan.net/rastrillo/fiesta/internal/harness" |
| 16 | ) |
| 17 | |
| 18 | // The shared drive helpers, under the names the ported drives are |
| 19 | // written with. One line each: the implementation is galleryrig's, |
| 20 | // which the history package uses too, so the two cannot drift. |
| 21 | // Fiesta's own drives keep newTree in a11y_browser_test.go, which |
| 22 | // records every request as well. |
| 23 | |
| 24 | func treeHandler(t *testing.T) http.Handler { |
| 25 | t.Helper() |
| 26 | return galleryrig.Tree(t, render(t, mountPath), mountPath+"/") |
| 27 | } |
| 28 | |
| 29 | func eagerly(t *testing.T, ctx context.Context, where string) { |
| 30 | t.Helper() |
| 31 | galleryrig.Eagerly(t, ctx, where) |
| 32 | } |
| 33 | |
| 34 | func mobileSettle(t *testing.T, ctx context.Context, where string) { |
| 35 | t.Helper() |
| 36 | galleryrig.MobileSettle(t, ctx, where) |
| 37 | } |
| 38 | |
| 39 | func addInit(js string) chromedp.Action { return galleryrig.AddInit(js) } |
| 40 | |
| 41 | func until(t *testing.T, ctx context.Context, where, expr string) { |
| 42 | t.Helper() |
| 43 | galleryrig.Until(t, ctx, where, expr) |
| 44 | } |
| 45 | |
| 46 | func phoneRig(t *testing.T, opts ...harness.Option) *harness.Rig { |
| 47 | t.Helper() |
| 48 | return galleryrig.PhoneRig(t, func() http.Handler { return treeHandler(t) }, opts...) |
| 49 | } |
| 50 | |
| 51 | func requireDrawnScrollbar(t *testing.T, ctx context.Context, where string) { |
| 52 | t.Helper() |
| 53 | galleryrig.RequireDrawnScrollbar(t, ctx, where) |
| 54 | } |
| 55 | |
| 56 | func requireCoarse(t *testing.T, ctx context.Context) { |
| 57 | t.Helper() |
| 58 | galleryrig.RequireCoarse(t, ctx) |
| 59 | } |
| 60 | |
| 61 | func settleMotion(t *testing.T, ctx context.Context, where string) { |
| 62 | t.Helper() |
| 63 | galleryrig.SettleMotion(t, ctx, where) |
| 64 | } |
| 65 | |
| 66 | func noScripts(t *testing.T, ctx context.Context) context.Context { |
| 67 | t.Helper() |
| 68 | return galleryrig.NoScripts(t, ctx) |
| 69 | } |
| 70 | |
| 71 | func requireScriptsOff(t *testing.T, ctx context.Context, where string) { |
| 72 | t.Helper() |
| 73 | galleryrig.RequireScriptsOff(t, ctx, where) |
| 74 | } |
| 75 | |
| 76 | // TestTheRigServesTheTreeAtItsMount: the shared tree answers for a |
| 77 | // file of the render under the mount and records a miss for a path |
| 78 | // it has not got, so a drive that frames a missing document fails by |
| 79 | // name. The bindings above are exercised by the drives that use them. |
| 80 | func TestTheRigServesTheTreeAtItsMount(t *testing.T) { |
| 81 | files := render(t, mountPath) |
| 82 | var missed []string |
| 83 | h := galleryrig.TreeRecording(t, files, mountPath+"/", func(p string) { missed = append(missed, p) }) |
| 84 | rig := harness.New(t, func(string) http.Handler { return h }) |
| 85 | rig.Allow(http.MethodGet, mountPath+"/no-such-file.html", http.StatusNotFound) |
| 86 | ctx, cancel := context.WithTimeout(rig.Context(), 30*time.Second) |
| 87 | defer cancel() |
| 88 | var title string |
| 89 | mustRun(t, ctx, |
| 90 | chromedp.Navigate(rig.Origin+href("day/en/index.html")), |
| 91 | chromedp.WaitReady("body"), |
| 92 | chromedp.Evaluate(`document.title`, &title), |
| 93 | ) |
| 94 | if title == "" { |
| 95 | t.Error("the Overview served through the rig has no title") |
| 96 | } |
| 97 | mustRun(t, ctx, chromedp.Navigate(rig.Origin+href("no-such-file.html")), chromedp.WaitReady("body")) |
| 98 | if len(missed) != 1 || missed[0] != href("no-such-file.html") { |
| 99 | t.Errorf("the rig recorded the misses %v, want the one path asked for", missed) |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | // TestTheSharedRigDrivesAPhone exercises every binding above on one |
| 104 | // page, so a helper that stopped matching the markup fails here rather |
| 105 | // than in the first ported drive that leans on it: a phone rig with a |
| 106 | // drawn scrollbar, an init script that runs before the page, the frames |
| 107 | // settled at a phone's width, motion waited out, a condition polled, |
| 108 | // and a second tab with scripts off that proves it. |
| 109 | func TestTheSharedRigDrivesAPhone(t *testing.T) { |
| 110 | rig := phoneRig(t, harness.WithScrollbars()) |
| 111 | ctx, cancel := context.WithTimeout(rig.Context(), 2*time.Minute) |
| 112 | defer cancel() |
| 113 | mustRun(t, ctx, |
| 114 | addInit(`window.__galleryInit = true`), |
| 115 | chromedp.ActionFunc(func(c context.Context) error { |
| 116 | return emulation.SetDeviceMetricsOverride(320, 640, 1, false).Do(c) |
| 117 | }), |
| 118 | chromedp.Navigate(rig.Origin+href("day/en/button.html")), |
| 119 | chromedp.WaitReady("body"), |
| 120 | ) |
| 121 | requireCoarse(t, ctx) |
| 122 | requireDrawnScrollbar(t, ctx, "button") |
| 123 | var init bool |
| 124 | mustRun(t, ctx, chromedp.Evaluate(`window.__galleryInit === true`, &init)) |
| 125 | if !init { |
| 126 | t.Error("the init script did not run before the page's own") |
| 127 | } |
| 128 | eagerly(t, ctx, "button") |
| 129 | mobileSettle(t, ctx, "button") |
| 130 | settleMotion(t, ctx, "button") |
| 131 | until(t, ctx, "button", `document.querySelectorAll(".ds-view__frame[data-gallery-painted]").length > 0`) |
| 132 | var on bool |
| 133 | mustRun(t, ctx, chromedp.Evaluate(`document.documentElement.hasAttribute("data-gallery-js")`, &on)) |
| 134 | if !on { |
| 135 | t.Error("the scripted tab did not run the gallery's script") |
| 136 | } |
| 137 | |
| 138 | off := noScripts(t, rig.Context()) |
| 139 | mustRun(t, off, chromedp.Navigate(rig.Origin+href("day/en/button.html")), chromedp.WaitReady("body")) |
| 140 | requireScriptsOff(t, off, "button with scripts off") |
| 141 | rig.Screen("main", "the button page on a phone") |
| 142 | } |
| 143 | |