package check import ( "encoding/json" "strings" "testing" "amadan.net/rastrillo/fiesta/internal/nodetest" ) // A tap on the back control writes its record before any navigation // commits. When the reader then cancels the leave-page prompt and // follows some other link, the next page must slide forward: the record // was for a navigation that never happened. The real Back still slides // back. Here cancelling the prompt is one stub call; a browser drive // would have to dismiss the dialog over CDP and race the next click // against it, so this runs shell.js itself against stub pages in Node. func TestACancelledBackDoesNotSlideTheNextPageBack(t *testing.T) { var got struct{ Taken, Cancelled string } if err := json.Unmarshal(nodetest.Run(t, nodetest.Cmd{Args: []string{"node/shell_node.mjs"}}), &got); err != nil { t.Fatal(err) } if got.Taken != "back" { t.Errorf("Back tapped and taken: the up page slid %q, want back", got.Taken) } if got.Cancelled != "forward" { t.Errorf("Back tapped, the prompt cancelled, then a link to another page followed: it slid %q, want forward", got.Cancelled) } } // A view transition the browser skips rejects its promises though the // navigation succeeds, and with nothing to handle them the page logged // an uncaught AbortError, which also failed any browser test that // watches the console. 1.2.0's shell.js (Rastrillo d9c9ed13, 023740c5, // e5e35a3e and 6b1c5478) swallows exactly a skipped transition's // rejection, at startup, on pagereveal and on pageswap, and lets every // other error through. node/transition_node.mjs hands it each kind. func TestShellJSSwallowsOnlyASkippedTransition(t *testing.T) { skippedTransitionsAreSwallowed(t, "shell.js") } // skippedTransitionsAreSwallowed runs node/transition_node.mjs against // one script and holds it to having checked every rejection: three // moments, three promises and eight reasons each, and one unrelated // rejection. func skippedTransitionsAreSwallowed(t *testing.T, script string) { t.Helper() var got struct{ Checked int } out := nodetest.Run(t, nodetest.Cmd{ Args: []string{"node/transition_node.mjs", script}, }) if err := json.Unmarshal(out, &got); err != nil { t.Fatal(err) } if want := 3*3*8 + 1; got.Checked != want { t.Errorf("node/transition_node.mjs checked %d rejections against "+ "%s, want %d", got.Checked, script, want) } } // transition.js is shell.js's transition handling alone, for a column, // stage or topbar page, which has no shell.js. It loads before the // stylesheets, because the browser can reject a skipped incoming // transition before pagereveal and a deferred script would miss it. func TestTransitionJSSwallowsOnlyASkippedTransition(t *testing.T) { skippedTransitionsAreSwallowed(t, "transition.js") } // TestTheTwoTransitionObserversAreTheSame: shell.js and transition.js // each carry the code that swallows a skipped transition's rejection, // because each is a file a page loads on its own, with no build step // between this tree and the bytes a server sends. The price of two // copies is drift, so the two are held identical: the WeakSet and the // function, character for character, but for the function's name. func TestTheTwoTransitionObserversAreTheSame(t *testing.T) { shell := transitionObserver(t, "shell.js", "observeTransition") alone := transitionObserver(t, "transition.js", "observe") renamed := strings.ReplaceAll(shell, "function observeTransition(", "function observe(") if renamed != alone { t.Errorf("the transition observer differs between shell.js and "+ "transition.js:\n--- shell.js\n%s\n--- transition.js\n%s", shell, alone) } } // transitionObserver is one script's observer: the WeakSet declared // straight before it and the function itself. It fails the test when // the script has no such pair, so the comparison is never of nothing. func transitionObserver(t *testing.T, script, name string) string { t.Helper() const set = "var observed = new WeakSet();\n" js := string(shippedScript(t, script)) body := functionBody(js, name) at := strings.Index(js, set+strings.Repeat(" ", 2)+"function "+name+"(") if body == "" || at < 0 { t.Fatalf("%s has no %q followed by function %s", script, strings.TrimSpace(set), name) } return set + body }