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, /