rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1// transition_node — runs shell.js or transition.js, the file named on
2// the command line, against a stub page in plain Node, and hands it view
3// transitions whose promises reject. A transition the browser skips
4// rejects ready, finished and updateCallbackDone with a DOMException
5// named AbortError, InvalidStateError or TimeoutError even though the
6// navigation succeeds, and nothing else handles it, so it reached the
7// console as an uncaught error. The script must swallow exactly those
8// three, at startup (document.activeViewTransition), on pagereveal and
9// on pageswap, and let every other rejection through: an Error, a
10// TypeError, a DOMException of another name, an Error merely named
11// AbortError, and null.
12//
13// Ported from Rastrillo's ui/shell_transition_node.mjs (d9c9ed13,
14// 023740c5, e5e35a3e, 6b1c5478). check/shell_test.go is the only
15// caller. A failed assertion exits non-zero with its message; a pass
16// prints one JSON object, the count of rejections checked, so the caller
17// can tell a run that checked them all from one that checked none.
18import assert from "node:assert/strict";
19import { readFileSync } from "node:fs";
20import vm from "node:vm";
21
22const script = process.argv[2];
23if (script !== "shell.js" && script !== "transition.js") {
24 throw new Error(`name shell.js or transition.js, not ${script}`);
25}
26const source = readFileSync(new URL(`../../js/${script}`, import.meta.url), "utf8");
27const listeners = {};
28const document = {
29 querySelector: () => null,
30 addEventListener() {},
31};
32const window = {
33 onpagereveal: null,
34 onpageswap: null,
35 addEventListener: (event, listener) => { listeners[event] = listener; },
36};
37const start = () => vm.runInNewContext(source, {
38 window, document, URL, DOMException,
39 location: new URL("https://app.test/"),
40});
41
42start();
43assert.equal(typeof listeners.pageswap, "function",
44 `${script} observes outgoing transitions, for a page that opts in with its own CSS`);
45
46const unhandled = [];
47process.on("unhandledRejection", (reason) => { unhandled.push(reason); });
48const turn = () => new Promise((resolve) => setImmediate(resolve));
49const skips = ["AbortError", "InvalidStateError", "TimeoutError"];
50let checked = 0;
51for (const event of ["startup", "pagereveal", "pageswap"]) {
52 for (const promise of ["ready", "finished", "updateCallbackDone"]) {
53 const reasons = skips.map((name) => new DOMException("No transition", name));
54 reasons.push(new Error("application failure"),
55 new TypeError("broken callback"), new DOMException("denied", "SecurityError"),
56 Object.assign(new Error("application abort"), { name: "AbortError" }), null);
57 for (const reason of reasons) {
58 unhandled.length = 0;
59 const transition = {
60 types: new Set(),
61 ready: Promise.resolve(),
62 finished: Promise.resolve(),
63 updateCallbackDone: Promise.resolve(),
64 };
65 transition[promise] = Promise.reject(reason);
66 if (event === "startup") {
67 // The same transition at startup and again on pagereveal is
68 // observed once, and the types are left as the page set them.
69 document.activeViewTransition = transition;
70 start();
71 assert.equal(transition.types.size, 0);
72 await turn();
73 listeners.pagereveal({ viewTransition: transition });
74 } else {
75 listeners[event]({ viewTransition: transition });
76 }
77 await turn();
78 await turn();
79 const skipped = reason instanceof DOMException && skips.includes(reason.name);
80 assert.deepEqual(unhandled, skipped ? [] : [reason], `${script}, ${event}.${promise}: ${reason}`);
81 checked++;
82 }
83 }
84}
85
86// A filter on every rejection would hide this unrelated application
87// failure, so the script must answer only the transitions it is given.
88unhandled.length = 0;
89const unrelated = new DOMException("application abort", "AbortError");
90Promise.reject(unrelated);
91await turn();
92await turn();
93assert.deepEqual(unhandled, [unrelated], `${script} swallowed a rejection that was not a transition's`);
94checked++;
95
96process.stdout.write(JSON.stringify({ checked }) + "\n");
97