AtlatestRepositorysigil-web-client
sigil-web-client / tree / test / integrationtest-web-client-drift.mjs
1
#!/usr/bin/env node3
import fs from "node:fs";4
import path from "node:path";5
import { spawnSync } from "node:child_process";6
import { fileURLToPath } from "node:url";7
import { WASI } from "node:wasi";9
const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");10
const app = path.join(repo, "test/integration/apps/web-client-drift");11
const build = path.join(app, "build/web");12
const sigil = process.env.SIGIL;13
const redirects = process.env.SIGIL_REDIRECTS;14
if (!sigil || !redirects) throw new Error("set SIGIL and SIGIL_REDIRECTS");16
const built = process.env.P5_SKIP_BUILD === "1" ? { status: 0, stdout: "", stderr: "" } : spawnSync(sigil, ["build", "--config", "web", "--force", "--no-bundle", "--redirects", redirects], {17
cwd: app,18
encoding: "utf8",19
env: { ...process.env, SIGIL_BACKEND: "native" },20
});21
process.stdout.write(built.stdout || "");22
process.stderr.write(built.stderr || "");23
if (built.status !== 0) throw new Error(`fixture build failed (${built.status})`);25
class Node {26
constructor(tag = "") {27
this.tagName = tag.toUpperCase();28
this.childNodes = [];29
this.parentNode = null;30
this.attributes = new Map();31
this.className = "";32
}33
appendChild(child) { return this.insertBefore(child, null); }34
insertBefore(child, ref) {35
if (child.parentNode) child.parentNode.removeChild(child);36
const index = ref === null ? this.childNodes.length : this.childNodes.indexOf(ref);37
if (index < 0) throw new Error("reference is not a child");38
this.childNodes.splice(index, 0, child);39
child.parentNode = this;40
return child;41
}42
removeChild(child) {43
const index = this.childNodes.indexOf(child);44
if (index < 0) throw new Error("node is not a child");45
this.childNodes.splice(index, 1);46
child.parentNode = null;47
return child;48
}49
replaceChildren(...children) {50
for (const child of [...this.childNodes]) this.removeChild(child);51
for (const child of children) this.appendChild(child);52
}53
setAttribute(name, value) {54
this.attributes.set(name, String(value));55
if (name === "class") this.className = String(value);56
}57
removeAttribute(name) { this.attributes.delete(name); }58
addEventListener() {}59
removeEventListener() {}60
}62
const root = new Node("div");63
const statusRoot = new Node("div");64
function walk(node, predicate) {65
if (predicate(node)) return node;66
for (const child of node.childNodes) {67
const found = walk(child, predicate);68
if (found) return found;69
}70
return null;71
}72
globalThis.document = {73
querySelector(selector) {74
if (selector === "#p5-root") return root;75
if (selector === "#p5-status") return statusRoot;76
if (selector === ".p5-child" || selector === ".p5-replacement") {77
return root.childNodes[0] || null;78
}79
if (selector.startsWith(".")) {80
const wanted = selector.slice(1);81
return walk(root, node => node.className.split(/\s+/).includes(wanted)) ||82
walk(statusRoot, node => node.className.split(/\s+/).includes(wanted));83
}84
return null;85
},86
createElement(tag) { return new Node(tag); },87
createTextNode(text) { const node = new Node(); node.textContent = String(text); return node; },88
};90
await import(path.join(build, "assets/sigil-wasm-dom.js"));91
const module = await WebAssembly.compile(fs.readFileSync(path.join(build, "web-client-drift.wasm")));92
const wasi = new WASI({ version: "preview1" });93
const imports = {94
...wasi.getImportObject(),95
...SigilWasmDom.createImports(),96
};97
for (const entry of WebAssembly.Module.imports(module)) {98
imports[entry.module] ||= {};99
if (!(entry.name in imports[entry.module]) && entry.kind === "function") {100
imports[entry.module][entry.name] = () => 0;101
}102
}103
const instance = await WebAssembly.instantiate(module, imports);104
SigilWasmDom.setInstance(instance);105
wasi.start(instance);106
const init = instance.exports.sigil_wasm_init();107
if (init !== 0) throw new Error(`sigil_wasm_init returned ${init}`);108
const start = instance.exports.sigil_wasm_start();109
if (start !== 0) throw new Error(`sigil_wasm_start returned ${start}`);110
if (root.childNodes.length !== 0) throw new Error("refused drift render mutated the root");111
if (statusRoot.childNodes.length !== 1) throw new Error("fixture did not reach its completion marker");112
console.log("P5-GATE: PASS fresh=#t undrifted=#t drifted=#f mutated=no");