| 1 | package check |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | |
| 8 | "amadan.net/rastrillo/fiesta/internal/nodetest" |
| 9 | ) |
| 10 | |
| 11 | // A tap on the back control writes its record before any navigation |
| 12 | // commits. When the reader then cancels the leave-page prompt and |
| 13 | // follows some other link, the next page must slide forward: the record |
| 14 | // was for a navigation that never happened. The real Back still slides |
| 15 | // back. Here cancelling the prompt is one stub call; a browser drive |
| 16 | // would have to dismiss the dialog over CDP and race the next click |
| 17 | // against it, so this runs shell.js itself against stub pages in Node. |
| 18 | func TestACancelledBackDoesNotSlideTheNextPageBack(t *testing.T) { |
| 19 | var got struct{ Taken, Cancelled string } |
| 20 | if err := json.Unmarshal(nodetest.Run(t, nodetest.Cmd{Args: []string{"node/shell_node.mjs"}}), &got); err != nil { |
| 21 | t.Fatal(err) |
| 22 | } |
| 23 | if got.Taken != "back" { |
| 24 | t.Errorf("Back tapped and taken: the up page slid %q, want back", got.Taken) |
| 25 | } |
| 26 | if got.Cancelled != "forward" { |
| 27 | t.Errorf("Back tapped, the prompt cancelled, then a link to another page followed: it slid %q, want forward", got.Cancelled) |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | // A view transition the browser skips rejects its promises though the |
| 32 | // navigation succeeds, and with nothing to handle them the page logged |
| 33 | // an uncaught AbortError, which also failed any browser test that |
| 34 | // watches the console. 1.2.0's shell.js (Rastrillo d9c9ed13, 023740c5, |
| 35 | // e5e35a3e and 6b1c5478) swallows exactly a skipped transition's |
| 36 | // rejection, at startup, on pagereveal and on pageswap, and lets every |
| 37 | // other error through. node/transition_node.mjs hands it each kind. |
| 38 | func TestShellJSSwallowsOnlyASkippedTransition(t *testing.T) { |
| 39 | skippedTransitionsAreSwallowed(t, "shell.js") |
| 40 | } |
| 41 | |
| 42 | // skippedTransitionsAreSwallowed runs node/transition_node.mjs against |
| 43 | // one script and holds it to having checked every rejection: three |
| 44 | // moments, three promises and eight reasons each, and one unrelated |
| 45 | // rejection. |
| 46 | func skippedTransitionsAreSwallowed(t *testing.T, script string) { |
| 47 | t.Helper() |
| 48 | var got struct{ Checked int } |
| 49 | out := nodetest.Run(t, nodetest.Cmd{ |
| 50 | Args: []string{"node/transition_node.mjs", script}, |
| 51 | }) |
| 52 | if err := json.Unmarshal(out, &got); err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | if want := 3*3*8 + 1; got.Checked != want { |
| 56 | t.Errorf("node/transition_node.mjs checked %d rejections against "+ |
| 57 | "%s, want %d", got.Checked, script, want) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // transition.js is shell.js's transition handling alone, for a column, |
| 62 | // stage or topbar page, which has no shell.js. It loads before the |
| 63 | // stylesheets, because the browser can reject a skipped incoming |
| 64 | // transition before pagereveal and a deferred script would miss it. |
| 65 | func TestTransitionJSSwallowsOnlyASkippedTransition(t *testing.T) { |
| 66 | skippedTransitionsAreSwallowed(t, "transition.js") |
| 67 | } |
| 68 | |
| 69 | // TestTheTwoTransitionObserversAreTheSame: shell.js and transition.js |
| 70 | // each carry the code that swallows a skipped transition's rejection, |
| 71 | // because each is a file a page loads on its own, with no build step |
| 72 | // between this tree and the bytes a server sends. The price of two |
| 73 | // copies is drift, so the two are held identical: the WeakSet and the |
| 74 | // function, character for character, but for the function's name. |
| 75 | func TestTheTwoTransitionObserversAreTheSame(t *testing.T) { |
| 76 | shell := transitionObserver(t, "shell.js", "observeTransition") |
| 77 | alone := transitionObserver(t, "transition.js", "observe") |
| 78 | renamed := strings.ReplaceAll(shell, "function observeTransition(", |
| 79 | "function observe(") |
| 80 | if renamed != alone { |
| 81 | t.Errorf("the transition observer differs between shell.js and "+ |
| 82 | "transition.js:\n--- shell.js\n%s\n--- transition.js\n%s", |
| 83 | shell, alone) |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | // transitionObserver is one script's observer: the WeakSet declared |
| 88 | // straight before it and the function itself. It fails the test when |
| 89 | // the script has no such pair, so the comparison is never of nothing. |
| 90 | func transitionObserver(t *testing.T, script, name string) string { |
| 91 | t.Helper() |
| 92 | const set = "var observed = new WeakSet();\n" |
| 93 | js := string(shippedScript(t, script)) |
| 94 | body := functionBody(js, name) |
| 95 | at := strings.Index(js, set+strings.Repeat(" ", 2)+"function "+name+"(") |
| 96 | if body == "" || at < 0 { |
| 97 | t.Fatalf("%s has no %q followed by function %s", script, |
| 98 | strings.TrimSpace(set), name) |
| 99 | } |
| 100 | return set + body |
| 101 | } |
| 102 | |