Run the cache drive where the cache works
TestADemoComesBackFromTheCacheInTheStoredScheme needs a real back/forward cache restore, which only full Chromium gives; the headless shell the fleet links as chromium never restores, and the Makefile gives full Chromium to check and internal/gallery/history only. In internal/gallery it would fail on the fleet naming the engine's reason rather than test the restore. It moves, unchanged in what it drives and asserts, into internal/gallery/history beside the other cache legs, with the four small helpers it needs there (the frame document's path, the manifest's default state, a fatal Run, a JSON read) and its two readings, schemeMarkJS and schemeOf. stage_browser_test.go keeps the first-paint drive, which needs no restore, and drops the strconv import the cache drive alone used. Run in the history role (make browser.internal.gallery.history, the FULL_CHROMIUM package): TestTheSchemeFollowsAPageBackFromTheCache, TestTheSwitchersAddressIsNeverLeftChanged and TestADemoComesBackFromTheCacheInTheStoredScheme all pass, the last in 4.4s with every return a real restore. Gated with make ci in a detached /var/tmp worktree at 992b61e with the two earlier fixes and these two files copied in, every role green.
Human
Prompt
Astra on Task 5's fixes: TestADemoComesBackFromTheCacheInTheStoredScheme (stage_browser_test.go:359) needs full Chromium but internal/gallery runs on the headless shell; move it into internal/gallery/history (the package Task 3 set up on full Chromium, Makefile:187), with whatever helper it needs there, and confirm it runs and passes in that role.
From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.
Summary
The demo cache drive moves from internal/gallery to internal/gallery/history, the package the Makefile runs on full Chromium, with the helpers it needs; it passes in that role.
Agent notes
frameDoc and defaultState are local to history because frameFile and mustDefault are test helpers of package gallery. If galleryrig grows a frame-path helper, both can use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+193
−126
internal/gallery/history/demo_cache_browser_test.go+190 −0internal/gallery/stage_browser_test.go+3 −126
CI passed — run details
CI log
=== gofmt ===
=== no-rastrillo ===
go test -run '^(TestFiestaNeverImportsRastrillo|TestThePublicPackagesLinkNoBrowser)$' -count=1 .
ok amadan.net/rastrillo/fiesta 0.107s
=== size ===
go test ./internal/build -run '^TestTheOneLinkIsUnder25KiBGzipped$' -count=1 -v
=== RUN TestTheOneLinkIsUnder25KiBGzipped
build_test.go:138: fiesta-plain.css: 152856 bytes, 22303 bytes gzipped (21.8 KiB of the 25.0 KiB limit)
build_test.go:138: fiesta-signal.css: 152951 bytes, 22414 bytes gzipped (21.9 KiB of the 25.0 KiB limit)
build_test.go:138: fiesta.css: 155770 bytes, 23018 bytes gzipped (22.5 KiB of the 25.0 KiB limit)
--- PASS: TestTheOneLinkIsUnder25KiBGzipped (0.02s)
PASS
ok amadan.net/rastrillo/fiesta/internal/build 0.020s
=== suite ===
--- shard 0 of 15 ---
==> shard 0 of 15: make root
go build ./...
go vet ./...
FIESTA_TEST_REQUIRE_NODE=1 FIESTA_TEST_REQUIRE_GIT=1 go test -count=1 ./...
ok amadan.net/rastrillo/fiesta 1.306s
ok amadan.net/rastrillo/fiesta/check 5.787s
ok amadan.net/rastrillo/fiesta/cmd/fiesta 91.426s
ok amadan.net/rastrillo/fiesta/example 0.230s
ok amadan.net/rastrillo/fiesta/internal/axe 0.175s
ok amadan.net/rastrillo/fiesta/internal/build 1.875s
ok amadan.net/rastrillo/fiesta/internal/codeview 0.010s
ok amadan.net/rastrillo/fiesta/internal/gallery 81.278s
? amadan.net/rastrillo/fiesta/internal/harness [no test files]
ok amadan.net/rastrillo/fiesta/internal/markup 0.007s
ok amadan.net/rastrillo/fiesta/internal/nodetest 0.460s
ok amadan.net/rastrillo/fiesta/internal/site 4.225s
ok amadan.net/rastrillo/fiesta/internal/tokens 0.007s
ok amadan.net/rastrillo/fiesta/internal/www 78.522s
--- shard 1 of 15 ---
==> shard 1 of 15: make staticcheck
go run honnef.co/go/tools/cmd/staticcheck@v0.8.2 -tags browser ./...
--- shard 2 of 15 ---
==> shard 2 of 15: make govulncheck
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
=== Symbol Results ===
No vulnerabilities found.
Your code is affected by 0 vulnerabilities.
This scan also found 2 vulnerabilities in packages you import and 5
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
--- shard 3 of 15 ---
==> shard 3 of 15: make gitleaks
go run github.com/zricethezav/gitleaks/v8@v8.30.1 git --no-banner --redact --log-opts='--full-history --diff-filter=tuxdb HEAD' .
[90m3:00AM[0m [32mINF[0m [1mUnknown SCM platform. Use --platform to include links in findings.[0m [36mhost=[0mamadan.net
[90m3:00AM[0m [32mINF[0m [1m1 commits scanned.[0m
[90m3:00AM[0m [32mINF[0m [1mscanned ~6231653 bytes (6.23 MB) in 4.31s[0m
[90m3:00AM[0m [32mINF[0m [1mno leaks found[0m
--- shard 4 of 15 ---
==> shard 4 of 15: make browser.internal.harness
./hack/gotest.sh browser 'internal.harness'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok amadan.net/rastrillo/fiesta/internal/harness 12.351s
--- shard 5 of 15 ---
==> shard 5 of 15: make browser.check~1of3
./hack/gotest.sh browser 'check~1of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 3 of ./check/ runs 95 of 286 tests
ok amadan.net/rastrillo/fiesta/check 228.183s
--- shard 6 of 15 ---
==> shard 6 of 15: make browser.check~2of3
./hack/gotest.sh browser 'check~2of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 3 of ./check/ runs 95 of 286 tests
ok amadan.net/rastrillo/fiesta/check 244.164s
--- shard 7 of 15 ---
==> shard 7 of 15: make browser.check~3of3
./hack/gotest.sh browser 'check~3of3'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 3 of ./check/ runs 96 of 286 tests
ok amadan.net/rastrillo/fiesta/check 247.192s
--- shard 8 of 15 ---
==> shard 8 of 15: make browser.internal.gallery~1of4
./hack/gotest.sh browser 'internal.gallery~1of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 4 of ./internal/gallery/ runs 1 of 104 tests
ok amadan.net/rastrillo/fiesta/internal/gallery 119.717s
--- shard 9 of 15 ---
==> shard 9 of 15: make browser.internal.gallery~2of4
./hack/gotest.sh browser 'internal.gallery~2of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 4 of ./internal/gallery/ runs 32 of 104 tests
ok amadan.net/rastrillo/fiesta/internal/gallery 106.336s
--- shard 10 of 15 ---
==> shard 10 of 15: make browser.internal.gallery~3of4
./hack/gotest.sh browser 'internal.gallery~3of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 3 of 4 of ./internal/gallery/ runs 35 of 104 tests
ok amadan.net/rastrillo/fiesta/internal/gallery 82.111s
--- shard 11 of 15 ---
==> shard 11 of 15: make browser.internal.gallery~4of4
./hack/gotest.sh browser 'internal.gallery~4of4'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 4 of 4 of ./internal/gallery/ runs 36 of 104 tests
ok amadan.net/rastrillo/fiesta/internal/gallery 101.321s
--- shard 12 of 15 ---
==> shard 12 of 15: make browser.internal.gallery.history
./hack/gotest.sh browser 'internal.gallery.history'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
ok amadan.net/rastrillo/fiesta/internal/gallery/history 34.555s
--- shard 13 of 15 ---
==> shard 13 of 15: make browser.internal.www~1of2
./hack/gotest.sh browser 'internal.www~1of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 1 of 2 of ./internal/www/ runs 17 of 34 tests
ok amadan.net/rastrillo/fiesta/internal/www 133.649s
--- shard 14 of 15 ---
==> shard 14 of 15: make browser.internal.www~2of2
./hack/gotest.sh browser 'internal.www~2of2'
gotest: on DOCS-T1, driving the harness's own choice (/usr/bin/chromium)
gotest: slice 2 of 2 of ./internal/www/ runs 17 of 34 tests
ok amadan.net/rastrillo/fiesta/internal/www 120.470s
Intent
diff --git a/internal/gallery/history/demo_cache_browser_test.go b/internal/gallery/history/demo_cache_browser_test.go| new file mode 100644 |
| index 0000000..175a6e6 |
| --- /dev/null |
| +++ b/internal/gallery/history/demo_cache_browser_test.go |
| @@ -0,0 +1,190 @@ |
| +//go:build browser |
| + |
| +package history |
| + |
| +import ( |
| + "context" |
| + "encoding/json" |
| + "net/http" |
| + "strconv" |
| + "testing" |
| + "time" |
| + |
| + "github.com/chromedp/chromedp" |
| + |
| + "amadan.net/rastrillo/fiesta" |
| + "amadan.net/rastrillo/fiesta/example" |
| + "amadan.net/rastrillo/fiesta/internal/harness" |
| +) |
| + |
| +// A shell demo opened in a tab of its own, left and come back to from |
| +// the back/forward cache, in the scheme stored while it was away. |
| + |
| +// frameDoc is the path of one state's frame document within the tree. |
| +func frameDoc(theme, locale, name, state string) string { |
| + return theme + "/" + locale + "/frames/" + name + "/" + state + ".html" |
| +} |
| + |
| +// defaultState is the manifest's default state for name. |
| +func defaultState(t *testing.T, name string) string { |
| + t.Helper() |
| + m, err := example.Load(fiesta.FS()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + state, ok := m.Default(name) |
| + if !ok || state == "" { |
| + t.Fatalf("the manifest has no default state for %s", name) |
| + } |
| + return state |
| +} |
| + |
| +func run(t *testing.T, ctx context.Context, actions ...chromedp.Action) { |
| + t.Helper() |
| + if err := chromedp.Run(ctx, actions...); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |
| + |
| +// readJSON evaluates a JS expression returning a JSON string into v. |
| +func readJSON(t *testing.T, ctx context.Context, js string, v any) { |
| + t.Helper() |
| + var raw string |
| + if err := chromedp.Run(ctx, chromedp.Evaluate(js, &raw)); err != nil { |
| + t.Fatalf("evaluating: %v\n%s", err, js) |
| + } |
| + if err := json.Unmarshal([]byte(raw), v); err != nil { |
| + t.Fatalf("decoding %q: %v", raw, err) |
| + } |
| +} |
| + |
| +// schemeMarkJS reports the scheme menu's buttons and which is marked. |
| +const schemeMarkJS = `JSON.stringify([...document.querySelectorAll("[rst-scheme] button")].map(b => ({ |
| + V: b.form && b.form.elements.scheme ? b.form.elements.scheme.value : "", |
| + Cur: b.getAttribute("aria-current") || "" |
| +})))` |
| + |
| +// schemeOf reads the root's scheme attribute and computed color-scheme. |
| +const schemeOf = `JSON.stringify({Theme: document.documentElement.getAttribute("data-theme") || "", Scheme: getComputedStyle(document.documentElement).colorScheme})` |
| + |
| +// TestADemoComesBackFromTheCacheInTheStoredScheme: a demo opened in a |
| +// tab of its own, left for the gallery, whose stored scheme changes |
| +// while it is away, comes back from the back/forward cache in the new |
| +// scheme with its menu marked: Dark to Light, then Light to System, on |
| +// the scheme menu's document; and Dark to Light on the sidebar demo, |
| +// left by a click into its page. The control first, every time: the |
| +// return really was a cache restore (pageshow.persisted, and the same |
| +// navigation start as before), since a document reloaded instead would |
| +// pass on start-up code alone and prove nothing about the pageshow |
| +// handler. Back is history.back() from the page itself, polled for: |
| +// chromedp's NavigateBack waits for a load a restored page never fires, |
| +// and its node waits never resolve after a history navigation |
| +// (check/menus_browser_test.go). |
| +// |
| +// Full Chromium only, which is why it is in this package: the headless |
| +// shell never restores a page from the cache (hack/gotest.sh's |
| +// FULL_CHROMIUM note), and on it this fails naming the engine's reason |
| +// rather than passing on a reload. |
| +func TestADemoComesBackFromTheCacheInTheStoredScheme(t *testing.T) { |
| + rig := harness.New(t, func(string) http.Handler { return treeHandler(t) }) |
| + ctx, cancel := context.WithTimeout(rig.Context(), 3*time.Minute) |
| + defer cancel() |
| + theme := fiesta.ThemeNames()[0] |
| + index := indexHref(theme, "en") |
| + menu := frameDoc(theme, "en", "scheme-menu", defaultState(t, "scheme-menu")) |
| + sidebar := frameDoc(theme, "en", "layout-sidebar", defaultState(t, "layout-sidebar")) |
| + page := frameDoc(theme, "en", "layout-sidebar-page", defaultState(t, "layout-sidebar-page")) |
| + |
| + run(t, ctx, |
| + addInit(`addEventListener("pageshow", function (e) { window.__restored = e.persisted; });`), |
| + chromedp.EmulateViewport(1280, 900), |
| + chromedp.Navigate(rig.Origin+index), |
| + chromedp.WaitReady("body"), |
| + chromedp.Evaluate(`localStorage.setItem("fiesta-scheme", "dark"); "ok"`, new(string)), |
| + ) |
| + |
| + readRoot := func(where, wantTheme, wantScheme string) { |
| + t.Helper() |
| + until(t, ctx, where, `document.readyState === "complete"`) |
| + var got struct{ Theme, Scheme string } |
| + readJSON(t, ctx, schemeOf, &got) |
| + if got.Theme != wantTheme { |
| + t.Errorf("%s: <html> carries data-theme=%q, want %q", where, got.Theme, wantTheme) |
| + } |
| + if got.Scheme != wantScheme { |
| + t.Errorf("%s: color-scheme computes %q, want %q", where, got.Scheme, wantScheme) |
| + } |
| + } |
| + readMark := func(where, scheme string) { |
| + t.Helper() |
| + var buttons []struct{ V, Cur string } |
| + readJSON(t, ctx, schemeMarkJS, &buttons) |
| + if len(buttons) == 0 { |
| + t.Fatalf("%s: no scheme menu button; the mark would be read off nothing", where) |
| + } |
| + for _, b := range buttons { |
| + if (b.V == scheme) != (b.Cur == "true") { |
| + t.Errorf("%s: the %s button is aria-current=%q; %s alone is marked", where, b.V, b.Cur, scheme) |
| + } |
| + } |
| + } |
| + // away leaves the document (leave is the statement that does), waits |
| + // to be at awayAt, changes the stored scheme there (store), goes |
| + // back, and requires the return to be a cache restore of the |
| + // document whose path ends in back. |
| + away := func(where, leave, awayAt, store, back string) { |
| + t.Helper() |
| + var started float64 |
| + run(t, ctx, chromedp.Evaluate(`performance.getEntriesByType("navigation")[0].startTime + performance.timeOrigin`, &started)) |
| + run(t, ctx, chromedp.Evaluate(`window.__restored = undefined; `+leave+`; true`, nil)) |
| + until(t, ctx, where+", away", `location.pathname.endsWith("`+awayAt+`") && document.readyState === "complete"`) |
| + run(t, ctx, chromedp.Evaluate(store+`; history.back(); true`, nil)) |
| + until(t, ctx, where+", back", `location.pathname.endsWith("`+back+`") && window.__restored !== undefined`) |
| + var raw string |
| + run(t, ctx, chromedp.Evaluate(`JSON.stringify({Restored: window.__restored === true, |
| + Started: performance.getEntriesByType("navigation")[0].startTime + performance.timeOrigin, |
| + Why: JSON.stringify((performance.getEntriesByType("navigation")[0] || {}).notRestoredReasons || null)})`, &raw)) |
| + var r struct { |
| + Restored bool |
| + Started float64 |
| + Why string |
| + } |
| + if err := json.Unmarshal([]byte(raw), &r); err != nil { |
| + t.Fatalf("%s: decoding %q: %v", where, raw, err) |
| + } |
| + if !r.Restored || r.Started != started { |
| + t.Fatalf("%s: the return was not a back/forward cache restore (persisted %v, navigation %s then %s; the engine says %s); the engine must be full Chromium and the tree handler must not send Cache-Control: no-store", |
| + where, r.Restored, strconv.FormatFloat(started, 'f', 0, 64), strconv.FormatFloat(r.Started, 'f', 0, 64), r.Why) |
| + } |
| + } |
| + |
| + // The scheme menu's document: Dark, then Light on the way back, then |
| + // System on the way back. |
| + run(t, ctx, chromedp.Navigate(rig.Origin+mountPrefix+menu), chromedp.WaitReady("body")) |
| + readRoot("the menu document", "dark", "dark") |
| + readMark("the menu document", "dark") |
| + toIndex := `location.assign("` + index + `")` |
| + menuPath := "/" + menu |
| + away("Dark to Light", toIndex, "/index.html", `localStorage.setItem("fiesta-scheme", "light")`, menuPath) |
| + readRoot("the menu document back from the cache with Light stored", "light", "light") |
| + readMark("the menu document back from the cache with Light stored", "light") |
| + away("Light to System", toIndex, "/index.html", `localStorage.removeItem("fiesta-scheme")`, menuPath) |
| + readRoot("the menu document back from the cache with nothing stored", "", "light dark") |
| + readMark("the menu document back from the cache with nothing stored", "system") |
| + |
| + // The shell demo: Dark, into its page by the nav, Light on the way back. |
| + run(t, ctx, |
| + chromedp.Evaluate(`localStorage.setItem("fiesta-scheme", "dark"); "ok"`, new(string)), |
| + chromedp.Navigate(rig.Origin+mountPrefix+sidebar), |
| + chromedp.WaitReady("body"), |
| + ) |
| + readRoot("the sidebar demo", "dark", "dark") |
| + away("the sidebar demo, Dark to Light", `document.querySelector("[rst-shell-nav] a").click()`, "/"+page, `localStorage.setItem("fiesta-scheme", "light")`, "/"+sidebar) |
| + readRoot("the sidebar demo back from the cache with Light stored", "light", "light") |
| + |
| + // A fresh navigation before the screen: after a history |
| + // navigation chromedp's node waits never resolve, so the flush of |
| + // the page's problems is taken on a document loaded anew. |
| + run(t, ctx, chromedp.Navigate(rig.Origin+mountPrefix+menu), chromedp.WaitReady("body")) |
| + rig.Screen("body", "the scheme menu's document in Light") |
| +} |
diff --git a/internal/gallery/stage_browser_test.go b/internal/gallery/stage_browser_test.go| index 4a26f15..6b02225 100644 |
| --- a/internal/gallery/stage_browser_test.go |
| +++ b/internal/gallery/stage_browser_test.go |
| @@ -7,7 +7,6 @@ import ( |
| "encoding/json" |
| "math" |
| "net/http" |
| - "strconv" |
| "strings" |
| "testing" |
| "time" |
| @@ -21,7 +20,9 @@ import ( |
| // The frame document as a browser lays it out: the padding the frame |
| // style gives a component and withholds from a whole page, the social |
| // sign-in card as an app would show it, and a demo opened in a tab of |
| -// its own in the reader's scheme before first paint and after Back. |
| +// its own in the reader's scheme before first paint. The same demo |
| +// coming back from the back/forward cache is in the history package, |
| +// on full Chromium. |
| // TestTheSocialSignInButtonsStackAndCarryTheirMarks: the social sign-in |
| // screen is markup an app copies, so what it looks like in the frame is |
| @@ -335,127 +336,3 @@ func TestADemoOpenedInItsOwnTabKeepsTheScheme(t *testing.T) { |
| readMark("the scheme menu's document") |
| rig.Screen("body", "the scheme menu's document in Dark") |
| } |
| - |
| -// schemeOf reads the root's scheme attribute and computed color-scheme. |
| -const schemeOf = `JSON.stringify({Theme: document.documentElement.getAttribute("data-theme") || "", Scheme: getComputedStyle(document.documentElement).colorScheme})` |
| - |
| -// TestADemoComesBackFromTheCacheInTheStoredScheme: a demo opened in a |
| -// tab of its own, left for the gallery, whose stored scheme changes |
| -// while it is away, comes back from the back/forward cache in the new |
| -// scheme with its menu marked: Dark to Light, then Light to System, on |
| -// the scheme menu's document; and Dark to Light on the sidebar demo, |
| -// left by a click into its page. The control first, every time: the |
| -// return really was a cache restore (pageshow.persisted, and the same |
| -// navigation start as before), since a document reloaded instead would |
| -// pass on start-up code alone and prove nothing about the pageshow |
| -// handler. Back is history.back() from the page itself, polled for: |
| -// chromedp's NavigateBack waits for a load a restored page never fires, |
| -// and its node waits never resolve after a history navigation |
| -// (check/menus_browser_test.go). |
| -// |
| -// Full Chromium only: the headless shell never restores a page from |
| -// the cache (hack/gotest.sh's FULL_CHROMIUM note), and on it this fails |
| -// naming the engine's reason rather than passing on a reload. |
| -func TestADemoComesBackFromTheCacheInTheStoredScheme(t *testing.T) { |
| - rig := harness.New(t, func(string) http.Handler { return newTree(t, nil) }) |
| - ctx, cancel := context.WithTimeout(rig.Context(), 3*time.Minute) |
| - defer cancel() |
| - theme := fiesta.ThemeNames()[0] |
| - index := href(theme + "/en/index.html") |
| - menu := frameFile(theme, "en", "scheme-menu", mustDefault(t, "scheme-menu")) |
| - sidebar := frameFile(theme, "en", "layout-sidebar", mustDefault(t, "layout-sidebar")) |
| - page := frameFile(theme, "en", "layout-sidebar-page", mustDefault(t, "layout-sidebar-page")) |
| - |
| - mustRun(t, ctx, |
| - addInit(`addEventListener("pageshow", function (e) { window.__restored = e.persisted; });`), |
| - chromedp.EmulateViewport(1280, 900), |
| - chromedp.Navigate(rig.Origin+index), |
| - chromedp.WaitReady("body"), |
| - chromedp.Evaluate(`localStorage.setItem("fiesta-scheme", "dark"); "ok"`, new(string)), |
| - ) |
| - |
| - readRoot := func(where, wantTheme, wantScheme string) { |
| - t.Helper() |
| - until(t, ctx, where, `document.readyState === "complete"`) |
| - var got struct{ Theme, Scheme string } |
| - at(t, ctx, schemeOf, &got) |
| - if got.Theme != wantTheme { |
| - t.Errorf("%s: <html> carries data-theme=%q, want %q", where, got.Theme, wantTheme) |
| - } |
| - if got.Scheme != wantScheme { |
| - t.Errorf("%s: color-scheme computes %q, want %q", where, got.Scheme, wantScheme) |
| - } |
| - } |
| - readMark := func(where, scheme string) { |
| - t.Helper() |
| - var buttons []struct{ V, Cur string } |
| - at(t, ctx, schemeMarkJS, &buttons) |
| - if len(buttons) == 0 { |
| - t.Fatalf("%s: no scheme menu button; the mark would be read off nothing", where) |
| - } |
| - for _, b := range buttons { |
| - if (b.V == scheme) != (b.Cur == "true") { |
| - t.Errorf("%s: the %s button is aria-current=%q; %s alone is marked", where, b.V, b.Cur, scheme) |
| - } |
| - } |
| - } |
| - // away leaves the document (leave is the statement that does), waits |
| - // to be at awayAt, changes the stored scheme there (store), goes |
| - // back, and requires the return to be a cache restore of the |
| - // document whose path ends in back. |
| - away := func(where, leave, awayAt, store, back string) { |
| - t.Helper() |
| - var started float64 |
| - mustRun(t, ctx, chromedp.Evaluate(`performance.getEntriesByType("navigation")[0].startTime + performance.timeOrigin`, &started)) |
| - mustRun(t, ctx, chromedp.Evaluate(`window.__restored = undefined; `+leave+`; true`, nil)) |
| - until(t, ctx, where+", away", `location.pathname.endsWith("`+awayAt+`") && document.readyState === "complete"`) |
| - mustRun(t, ctx, chromedp.Evaluate(store+`; history.back(); true`, nil)) |
| - until(t, ctx, where+", back", `location.pathname.endsWith("`+back+`") && window.__restored !== undefined`) |
| - var raw string |
| - mustRun(t, ctx, chromedp.Evaluate(`JSON.stringify({Restored: window.__restored === true, |
| - Started: performance.getEntriesByType("navigation")[0].startTime + performance.timeOrigin, |
| - Why: JSON.stringify((performance.getEntriesByType("navigation")[0] || {}).notRestoredReasons || null)})`, &raw)) |
| - var r struct { |
| - Restored bool |
| - Started float64 |
| - Why string |
| - } |
| - if err := json.Unmarshal([]byte(raw), &r); err != nil { |
| - t.Fatalf("%s: decoding %q: %v", where, raw, err) |
| - } |
| - if !r.Restored || r.Started != started { |
| - t.Fatalf("%s: the return was not a back/forward cache restore (persisted %v, navigation %s then %s; the engine says %s); the engine must be full Chromium and the tree handler must not send Cache-Control: no-store", |
| - where, r.Restored, strconv.FormatFloat(started, 'f', 0, 64), strconv.FormatFloat(r.Started, 'f', 0, 64), r.Why) |
| - } |
| - } |
| - |
| - // The scheme menu's document: Dark, then Light on the way back, then |
| - // System on the way back. |
| - mustRun(t, ctx, chromedp.Navigate(rig.Origin+href(menu)), chromedp.WaitReady("body")) |
| - readRoot("the menu document", "dark", "dark") |
| - readMark("the menu document", "dark") |
| - toIndex := `location.assign("` + index + `")` |
| - menuPath := "/" + menu |
| - away("Dark to Light", toIndex, "/index.html", `localStorage.setItem("fiesta-scheme", "light")`, menuPath) |
| - readRoot("the menu document back from the cache with Light stored", "light", "light") |
| - readMark("the menu document back from the cache with Light stored", "light") |
| - away("Light to System", toIndex, "/index.html", `localStorage.removeItem("fiesta-scheme")`, menuPath) |
| - readRoot("the menu document back from the cache with nothing stored", "", "light dark") |
| - readMark("the menu document back from the cache with nothing stored", "system") |
| - |
| - // The shell demo: Dark, into its page by the nav, Light on the way back. |
| - mustRun(t, ctx, |
| - chromedp.Evaluate(`localStorage.setItem("fiesta-scheme", "dark"); "ok"`, new(string)), |
| - chromedp.Navigate(rig.Origin+href(sidebar)), |
| - chromedp.WaitReady("body"), |
| - ) |
| - readRoot("the sidebar demo", "dark", "dark") |
| - away("the sidebar demo, Dark to Light", `document.querySelector("[rst-shell-nav] a").click()`, "/"+page, `localStorage.setItem("fiesta-scheme", "light")`, "/"+sidebar) |
| - readRoot("the sidebar demo back from the cache with Light stored", "light", "light") |
| - |
| - // A fresh navigation before the screen: after a history |
| - // navigation chromedp's node waits never resolve, so the flush of |
| - // the page's problems is taken on a document loaded anew. |
| - mustRun(t, ctx, chromedp.Navigate(rig.Origin+href(menu)), chromedp.WaitReady("body")) |
| - rig.Screen("body", "the scheme menu's document in Light") |
| -} |