Add CLAUDE.md and the test harness
CLAUDE.md records the Nova constraints this extension is built around — established by reading Nova 14.1's bundle and probing its preview server, and expensive to re-derive: no previewer extension point, generated pages must live under the preview root, dot-directories are served, and no API reaches into a preview WebView. It also flags the one unverified assumption, that a tab showing Preview still appears in nova.workspace.textEditors, with its symptom and fix. Tests/ is the harness that built the extension, kept rather than left in a scratchpad: a stubbed Nova runtime backed by the real filesystem, node suites for generation and the four close paths, and a headless-browser render test that skips itself without a browser. It caught the ./ doubling in rebased links and checkbox state not surviving serialization. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01TueXpMi7eAtgWPuMr4ii52
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
// Renders a fixture through the real render.js in a headless browser, because
|
||||
// everything the page does — Markdown, Mermaid, the DOM fix-ups — only happens
|
||||
// where there is a DOM. Skips itself when no browser can be found.
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const { execFileSync } = require("child_process");
|
||||
|
||||
const EXT = path.join(__dirname, "..");
|
||||
const { buildDocument } = require(path.join(EXT, "Scripts", "render.js"));
|
||||
|
||||
function findBrowser() {
|
||||
if (process.env.MERMAID_PREVIEW_CHROME) return process.env.MERMAID_PREVIEW_CHROME;
|
||||
|
||||
const cache = path.join(os.homedir(), "Library", "Caches", "ms-playwright");
|
||||
if (fs.existsSync(cache)) {
|
||||
for (const entry of fs.readdirSync(cache)) {
|
||||
const candidate = path.join(cache, entry, "chrome-headless-shell-mac-arm64", "chrome-headless-shell");
|
||||
if (fs.existsSync(candidate)) return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
const chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
||||
return fs.existsSync(chrome) ? chrome : null;
|
||||
}
|
||||
|
||||
const browser = findBrowser();
|
||||
if (!browser) {
|
||||
console.log("SKIP no headless browser found (set MERMAID_PREVIEW_CHROME to one)");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const work = path.join(__dirname, ".tmp", "render");
|
||||
fs.rmSync(work, { recursive: true, force: true });
|
||||
fs.mkdirSync(path.join(work, "out", "assets", "vendor"), { recursive: true });
|
||||
fs.mkdirSync(path.join(work, "docs"), { recursive: true });
|
||||
|
||||
for (const name of ["preview.css", "preview.js"]) {
|
||||
fs.copyFileSync(path.join(EXT, "Assets", name), path.join(work, "out", "assets", name));
|
||||
}
|
||||
for (const name of ["markdown-it.min.js", "mermaid.min.js"]) {
|
||||
fs.copyFileSync(path.join(EXT, "Assets", "vendor", name), path.join(work, "out", "assets", "vendor", name));
|
||||
}
|
||||
|
||||
const markdown = fs.readFileSync(path.join(__dirname, "fixtures", "sample.md"), "utf8");
|
||||
fs.writeFileSync(path.join(work, "docs", "sample.md"), markdown);
|
||||
|
||||
const results = [];
|
||||
const check = (name, ok) => results.push([ok ? "PASS" : "FAIL", name]);
|
||||
|
||||
function render(theme) {
|
||||
const page = path.join(work, "out", `sample-${theme}.html`);
|
||||
fs.writeFileSync(
|
||||
page,
|
||||
buildDocument({
|
||||
markdown,
|
||||
title: "sample.md",
|
||||
sourcePath: path.join(work, "docs", "sample.md"),
|
||||
sourceRelDir: "../docs/",
|
||||
assetsRelDir: "assets",
|
||||
mode: "markdown",
|
||||
theme,
|
||||
contentWidth: 860,
|
||||
})
|
||||
);
|
||||
return execFileSync(
|
||||
browser,
|
||||
["--headless", "--disable-gpu", "--no-sandbox", "--virtual-time-budget=10000", "--dump-dom", `file://${page}`],
|
||||
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 32 * 1024 * 1024 }
|
||||
);
|
||||
}
|
||||
|
||||
const dom = render("auto");
|
||||
|
||||
// Only the rendered <main>. The Markdown source is embedded further down the
|
||||
// page as data, so a naive slice to end-of-document would find anything.
|
||||
const body = dom.slice(dom.indexOf('id="content"'), dom.indexOf('id="preview-data"'));
|
||||
|
||||
check("both valid diagrams drawn", (dom.match(/<svg/g) || []).length === 2);
|
||||
check("the broken diagram reports in place", dom.includes("mermaid-error-message") && dom.includes("is-error"));
|
||||
check("front matter is not rendered", !body.includes("Front matter should be hidden"));
|
||||
check("headings get ids", dom.includes('<h2 id="a-second-heading"'));
|
||||
check("in-document anchors are left alone", dom.includes('href="#a-second-heading"'));
|
||||
check("relative links are rebased once", dom.includes('href="../docs/other.md"') && !dom.includes("docs/./"));
|
||||
check("relative images are rebased", dom.includes('src="../docs/img/logo.png"'));
|
||||
check("task list becomes checkboxes", (dom.match(/<input type="checkbox"/g) || []).length === 2);
|
||||
check("checked state survives", dom.includes('checked=""'));
|
||||
check("wide tables scroll on their own", dom.includes('class="table-scroll"'));
|
||||
check("content is revealed", !body.includes('aria-busy="true"'));
|
||||
|
||||
const dark = render("dark");
|
||||
check("dark theme reaches the page", dark.includes('data-theme="dark"'));
|
||||
check("dark theme still draws diagrams", (dark.match(/<svg/g) || []).length === 2);
|
||||
|
||||
for (const [status, name] of results) console.log(`${status} ${name}`);
|
||||
process.exitCode = results.some(([s]) => s === "FAIL") ? 1 : 0;
|
||||
Reference in New Issue
Block a user