import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; import vm from "node:vm"; import { commandsFor, pathsFor, renderPage } from "../cf-worker/src/view.js"; import { makeSignedDocument, parsePublishedSignedRelease } from "../cf-worker/src/signed-release.js"; 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"); const config = { baseUrl: "https://example.test", repositoryUrl: "https://github.com/example/repo", defaultCron: "*/15 * * * *" }; const commands = commandsFor(config); 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({ commandTexts = commands.map(({command}) => command), savedTheme, brokenStorage = false, clipboard = async () => {}, missingTarget = false } = {}) { const root = new Element("root"); const ids = ["theme-picker", "copy-status", "copy-fallback", "fallback-text", "close-copy-fallback"]; const elements = new Map(ids.map((id) => [id, new Element(id)])); const codeElements = commandTexts.map((text, index) => new Element(`command-${index}`, text)); 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); if (missingTarget) elements.delete(codeElements[0].id); const requestedIds = []; const document = { documentElement: root, getElementById: (id) => { requestedIds.push(id); return elements.get(id) || null; }, querySelectorAll: (selector) => ({ ".code-block pre": pres, "[data-copy-target]": buttons })[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, codeElements, buttons, pres, stored, requestedIds, flushTimers: () => { const pending = [...timers.values()]; timers.clear(); pending.forEach((callback) => callback()); } }; } function snapshot(overrides = {}) { return {revision: "a".repeat(40), publishedAt: "2026-10-12T00:00:00Z", keys: [], keyCount: 1, sha256: "b".repeat(64), keyRevision: "c".repeat(64), commands, ...config, signedRelease: {listId: "test-list"}, ...overrides}; } function embeddedMetadata(page) { return JSON.parse(page.match(/'}, keyCount: empty ? 0 : 1, allowEmpty: type === "explicit-empty", sha256: empty ? null : "b".repeat(64), keyRevision: empty ? null : "c".repeat(64)}); const page = renderPage(html, state); const embedded = embeddedMetadata(page); assert.equal(embedded.sourceStatus, status); assert.equal(embedded.keyCount, empty ? 0 : 1); assert.equal(embedded.signaturePresent, false); assert.equal(embedded.signatureStatus, "未提供签名包"); assert.doesNotMatch(page, /