rastrillo / fiesta Public

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

Plain git — no account needed to clone.

Download

Download this file

1// shell_node — runs shell.js against a stub page in plain Node, one
2// fresh script per page and one sessionStorage for the tab, so a record
3// one page writes is what the next page reads, as in a browser. Only
4// the direction is driven: what the back control's click leaves behind
5// and which way the next page's transition is typed.
6//
7// check/shell_test.go is the only caller. It reads back one JSON
8// object: each scenario's name and the transition type the last page
9// got.
10//
11// This file lives under check/node/ beside the other Node drivers, so
12// `go test` in check/ finds it by name; it is never embedded, and
13// nothing in fiesta.go reaches for *.mjs.
14import { readFileSync } from "node:fs";
15import vm from "node:vm";
16
17const src = readFileSync(new URL("../../js/shell.js", import.meta.url), "utf8");
18
19// A page: its URL, its view ("page" or "index"), and the href of its
20// back control, if it has one. No Navigation API, so the click never
21// takes the history.back() path and always leaves its record behind.
22function open(storage, url, view, backHref) {
23 const on = { window: {}, document: {} };
24 const location = new URL(url);
25 const root = { matches: (sel) => sel.includes(`~="${view}"`) };
26 const back = backHref && {
27 href: new URL(backHref, url).href,
28 closest: (sel) => (sel.includes("[rst-shell-back]") ? back : null),
29 };
30 const document = {
31 prerendering: false,
32 querySelector: (sel) => (sel.includes("[rst-shell-sidebar]") ? root : null),
33 querySelectorAll: () => [],
34 getElementById: () => null,
35 addEventListener: (type, fn) => { on.document[type] = fn; },
36 };
37 const window = {
38 sessionStorage: storage,
39 onpagereveal: null,
40 onpageswap: null,
41 addEventListener: (type, fn) => { on.window[type] = fn; },
42 };
43 vm.runInNewContext(src, { window, document, location, URL, matchMedia: () => ({ matches: true }) });
44 return {
45 tapBack() {
46 on.document.click({ target: back, button: 0, defaultPrevented: false, preventDefault() {} });
47 },
48 leave() { on.window.pageswap({}); },
49 reveal() {
50 const types = new Set();
51 on.window.pagereveal({ viewTransition: { types } });
52 return [...types].join(",");
53 },
54 };
55}
56
57function tab() {
58 const m = new Map();
59 return {
60 getItem: (k) => (m.has(k) ? m.get(k) : null),
61 setItem: (k, v) => { m.set(k, String(v)); },
62 removeItem: (k) => { m.delete(k); },
63 };
64}
65
66const out = {};
67
68// Back is tapped and the page really goes back to its up page.
69{
70 const s = tab();
71 const p = open(s, "https://app.test/invoices", "page", "/#nav-invoices");
72 p.tapBack();
73 p.leave();
74 out.taken = open(s, "https://app.test/#nav-invoices", "index").reveal();
75}
76
77// Back is tapped, the reader cancels the leave-page prompt, then follows
78// a link to another content page: that is a forward navigation.
79{
80 const s = tab();
81 const p = open(s, "https://app.test/invoices", "page", "/");
82 p.tapBack();
83 p.leave();
84 out.cancelled = open(s, "https://app.test/orders", "page", "/").reveal();
85}
86
87process.stdout.write(JSON.stringify(out));
88