import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; import vm from "node:vm"; const script = await readFile(new URL("../site/assets/app.js", import.meta.url), "utf8"); const html = await readFile(new URL("../site/index.html", import.meta.url), "utf8"); const css = await readFile(new URL("../site/assets/style.css", import.meta.url), "utf8"); class Element { constructor(id, text = "") { this.id = id; this.textContent = text; this.dataset = {}; this.attributes = new Map(); this.listeners = new Map(); this.classList = { add: (name) => this.attributes.set("class", name) }; this.disabled = false; this.open = false; this.value = ""; this.focused = false; this.selected = false; } setAttribute(name, value) { this.attributes.set(name, value); } getAttribute(name) { return this.attributes.get(name) ?? null; } removeAttribute(name) { this.attributes.delete(name); } addEventListener(name, handler) { this.listeners.set(name, handler); } dispatch(name) { return this.listeners.get(name)?.({ target: this }); } focus() { this.focused = true; } select() { this.selected = true; } showModal() { this.open = true; } close() { this.open = false; this.dispatch("close"); } } function setup({ keys = ["ssh-ed25519 AAAA exact comment"], savedTheme, brokenStorage = false, clipboard = async () => {}, rawRelease } = {}) { const root = new Element("root"); const ids = ["theme-picker", "copy-status", "copy-fallback", "fallback-text", "close-copy-fallback", "copy-all-keys", "release-data"]; const elements = new Map(ids.map((id) => [id, new Element(id)])); const keyElements = keys.map((text, index) => new Element(`key-${index}`, text)); const codeElements = [...keyElements, new Element("command-install", "curl -fsSL https://example.test/script.sh | sh -s -- -c")]; const pres = codeElements.map((code) => new Element(`pre-${code.id}`)); const buttons = codeElements.map((code) => { const button = new Element(`copy-${code.id}`); button.dataset.copyTarget = code.id; button.setAttribute("aria-label", `复制 ${code.id}`); return button; }); for (const element of codeElements) elements.set(element.id, element); elements.get("release-data").textContent = rawRelease ?? JSON.stringify({ keys: keys.map((text) => ({ text })) }); elements.get("copy-all-keys").setAttribute("aria-label", "复制全部公钥"); const document = { documentElement: root, getElementById: (id) => elements.get(id) || null, querySelectorAll: (selector) => ({ ".code-block pre": pres, "[data-copy-target]": buttons, ".key-card pre code": keyElements, })[selector] || [], }; const stored = new Map(savedTheme ? [["quickly-conf-sshd-theme", savedTheme]] : []); const timers = new Map(); let timerId = 0; const context = vm.createContext({ document, navigator: clipboard === null ? {} : { clipboard: { writeText: clipboard } }, localStorage: { getItem: (key) => { if (brokenStorage) throw new Error("Denied"); return stored.get(key) || null; }, setItem: (key, value) => { if (brokenStorage) throw new Error("Denied"); stored.set(key, value); }, }, setTimeout: (callback) => { timers.set(++timerId, callback); return timerId; }, clearTimeout: (id) => timers.delete(id), }); vm.runInContext(script, context); return { root, elements, keyElements, codeElements, buttons, pres, stored, flushTimers: () => { const pending = [...timers.values()]; timers.clear(); pending.forEach((callback) => callback()); } }; } test("homepage prerenders content, release metadata, and independent local assets", () => { for (const token of ["RELEASE_JSON", "KEYS_HTML", "COMMANDS_HTML", "REVISION", "PUBLISHED_AT", "KEY_COUNT", "REPOSITORY_URL", "RELEASE_LABEL", "PREVIEW_NOTICE", "SOURCE_LABEL", "SOURCE_STATUS", "CONTENT_REVISION", "KEYS_NOTICE"]) { assert.ok(html.includes(`@@${token}@@`), `${token} placeholder is required`); } assert.match(html, //); assert.match(html, /href="\/assets\/style\.css"/); assert.match(html, /src="\/assets\/app\.js" defer/); assert.match(html, /