mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
184 lines
8.9 KiB
JavaScript
184 lines
8.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { test } from "node:test";
|
|
import { description, siteUrl } from "../src/lib/site.ts";
|
|
|
|
const base = process.env.DOCS_TEST_URL;
|
|
assert.ok(base, "Set DOCS_TEST_URL to a running production build.");
|
|
const pages = JSON.parse(await readFile(new URL("./fixtures/migration-baseline.json", import.meta.url), "utf8"));
|
|
const get = (path, headers = {}) => fetch(new URL(path, base), {
|
|
redirect: "manual",
|
|
signal: AbortSignal.timeout(30_000),
|
|
headers: { "user-agent": "Mozilla/5.0", accept: "text/html", ...headers },
|
|
});
|
|
const decode = (text) => text.replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
const meta = (html, key) => decode(html.match(new RegExp(`<meta (?:name|property)="${key}" content="([^"]*)"`))?.[1] ?? "");
|
|
|
|
for (const { href, title, headings } of pages) {
|
|
test(`${href} preserves its title, social card and published heading anchors`, async () => {
|
|
const response = await get(href);
|
|
assert.equal(response.status, 200);
|
|
const html = await response.text();
|
|
assert.equal(decode(html.match(/<title>([^<]*)<\/title>/)?.[1] ?? ""), `${title} | scriptc`);
|
|
assert.equal(meta(html, "description"), description);
|
|
assert.equal(meta(html, "og:title"), `${title} | scriptc`);
|
|
assert.ok(html.includes(`<link rel="canonical" href="${siteUrl}${href}"`));
|
|
assert.equal((html.match(/<h1\b/g) ?? []).length, 1);
|
|
assert.ok(html.includes('data-geistdocs-article="title"'));
|
|
assert.ok(html.includes('data-geistdocs-article="body"'));
|
|
for (const heading of headings) assert.ok(html.includes(`id="${heading.id}"`), `missing anchor ${heading.id}`);
|
|
});
|
|
}
|
|
|
|
test("previous docs URLs redirect permanently and preserve query parameters", async () => {
|
|
for (const { legacy, href } of pages) {
|
|
const response = await get(`${legacy}?mode=static`);
|
|
assert.equal(response.status, 308, legacy);
|
|
const destination = new URL(response.headers.get("location"), base);
|
|
assert.equal(destination.pathname, href);
|
|
assert.equal(destination.search, "?mode=static");
|
|
}
|
|
assert.equal((await get("/docs/introduction")).headers.get("location"), "/docs");
|
|
});
|
|
|
|
test("Markdown twins preserve examples and declare the HTML canonical URL", async () => {
|
|
for (const { href } of pages) {
|
|
const response = await get(`${href}.md`);
|
|
assert.equal(response.status, 200, href);
|
|
assert.match(response.headers.get("content-type"), /^text\/markdown/);
|
|
assert.ok(response.headers.get("link")?.includes(`<${siteUrl}${href}>; rel="canonical"`));
|
|
const markdown = await response.text();
|
|
const file = href === "/docs" ? "index" : href.slice("/docs/".length);
|
|
const source = await readFile(new URL(`../content/docs/${file}.mdx`, import.meta.url), "utf8");
|
|
for (const fence of source.matchAll(/^```[^\n]*\n([\s\S]*?)^```/gm)) {
|
|
assert.ok(markdown.includes(fence[1].trimEnd()), `${file} lost a code example`);
|
|
}
|
|
assert.doesNotMatch(markdown, /className=/);
|
|
}
|
|
const mdx = await get("/docs/quickstart.mdx");
|
|
assert.equal(mdx.status, 200);
|
|
assert.match(mdx.headers.get("content-type"), /^text\/markdown/);
|
|
});
|
|
|
|
test("literal HTML tables and definition lists remain readable", async () => {
|
|
const intro = await (await get("/docs")).text();
|
|
assert.match(intro, /<table[ >]/);
|
|
const cli = await (await get("/docs/cli")).text();
|
|
assert.match(cli, /<dl[ >]/);
|
|
assert.match(cli, /<dt[ >]/);
|
|
const markdown = await (await get("/docs/cli.md")).text();
|
|
assert.ok(markdown.includes("--dynamic"));
|
|
assert.ok(markdown.includes("--external-types"));
|
|
});
|
|
|
|
test("docs negotiate Markdown and keep browser HTML and prefetch payloads", async () => {
|
|
for (const headers of [{ accept: "text/markdown" }, { "user-agent": "ClaudeBot/1.0", accept: "*/*" }]) {
|
|
const response = await get("/docs/quickstart", headers);
|
|
assert.equal(response.status, 200);
|
|
assert.match(response.headers.get("content-type"), /^text\/markdown/);
|
|
await response.body?.cancel();
|
|
}
|
|
const html = await get("/docs/quickstart");
|
|
assert.match(html.headers.get("content-type"), /^text\/html/);
|
|
await html.body?.cancel();
|
|
// Next.js can add its _rsc hash with a redirect before returning the payload.
|
|
const prefetch = await fetch(new URL("/docs/quickstart", base), {
|
|
signal: AbortSignal.timeout(30_000),
|
|
headers: { rsc: "1", "next-router-prefetch": "1", accept: "*/*", "user-agent": "ClaudeBot/1.0" },
|
|
});
|
|
assert.equal(prefetch.status, 200);
|
|
assert.doesNotMatch(prefetch.headers.get("content-type"), /markdown/);
|
|
await prefetch.body?.cancel();
|
|
});
|
|
|
|
test("unknown pages return 404 in HTML and Markdown", async () => {
|
|
for (const path of ["/docs/missing-page", "/docs/missing-page.md", "/missing-page"]) {
|
|
const response = await get(path);
|
|
assert.equal(response.status, 404, path);
|
|
await response.body?.cancel();
|
|
}
|
|
});
|
|
|
|
test("agent and search indexes include every documentation page", async () => {
|
|
const llms = await (await get("/llms.txt")).text();
|
|
const sitemapMarkdown = await (await get("/sitemap.md")).text();
|
|
const sitemap = await (await get("/sitemap.xml")).text();
|
|
for (const { href } of pages) {
|
|
assert.ok(llms.includes(`${siteUrl}${href}`), `llms.txt lacks ${href}`);
|
|
assert.ok(sitemapMarkdown.includes(href), `sitemap.md lacks ${href}`);
|
|
assert.ok(sitemap.includes(`<loc>${siteUrl}${href}</loc>`), `sitemap.xml lacks ${href}`);
|
|
}
|
|
const agents = await (await get("/agents.md")).text();
|
|
assert.ok(agents.includes("scriptc"));
|
|
assert.ok(agents.includes("/compatibility"));
|
|
const search = await get("/api/search?query=runtime%20type%20checks");
|
|
assert.equal(search.status, 200);
|
|
assert.ok((await search.json()).some((result) => result.url === "/docs/limitations#runtime-type-checks"));
|
|
});
|
|
|
|
test("internal documentation links and their fragments resolve", async () => {
|
|
const anchors = new Map();
|
|
for (const { href } of pages) anchors.set(href, await (await get(href)).text());
|
|
for (const { href } of pages) {
|
|
const file = href === "/docs" ? "index" : href.slice("/docs/".length);
|
|
const source = await readFile(new URL(`../content/docs/${file}.mdx`, import.meta.url), "utf8");
|
|
for (const [, path, fragment] of source.matchAll(/\]\((\/docs(?:\/[\w-]+)?)(?:#([\w-]+))?\)/g)) {
|
|
assert.ok(anchors.has(path), `${href} links to missing page ${path}`);
|
|
if (fragment) assert.ok(anchors.get(path).includes(`id="${fragment}"`), `${href} links to missing fragment ${path}#${fragment}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("compatibility page and data endpoints keep their existing contracts", async () => {
|
|
const page = await get("/compatibility");
|
|
assert.equal(page.status, 200);
|
|
const html = await page.text();
|
|
assert.ok(html.includes("Node.js 24 Compatibility"));
|
|
assert.ok(html.includes(`<link rel="canonical" href="${siteUrl}/compatibility"`));
|
|
const response = await get("/compatibility/data");
|
|
assert.equal(response.status, 200);
|
|
const dataset = await response.json();
|
|
const metadata = JSON.parse(await readFile(new URL("../src/generated/node-v24-compatibility-meta.json", import.meta.url), "utf8"));
|
|
assert.equal(dataset.rows.length, metadata.rowCount);
|
|
assert.equal((await get("/node-compatibility")).status, 308);
|
|
assert.equal((await get("/node-compatibility/data")).status, 308);
|
|
});
|
|
|
|
test("WebMCP serves its bridge and discovery manifest", async () => {
|
|
const response = await get("/api/mcp?webmcp-script");
|
|
assert.equal(response.status, 200);
|
|
assert.match(response.headers.get("content-type"), /javascript/);
|
|
assert.match(await response.text(), /registerTool/);
|
|
const manifest = await get("/.well-known/mcp.json");
|
|
assert.equal(manifest.status, 200);
|
|
assert.ok((await manifest.json()).servers.some((server) => server.url.endsWith("/api/mcp")));
|
|
});
|
|
|
|
test("homepage negotiates the agent profile and preserves explicit HTML requests", async () => {
|
|
for (const headers of [{ accept: "text/markdown" }, { "user-agent": "ClaudeBot/1.0", accept: "*/*" }]) {
|
|
const response = await get("/", headers);
|
|
assert.equal(response.status, 200);
|
|
assert.match(response.headers.get("content-type"), /^text\/markdown/);
|
|
assert.ok((await response.text()).includes("/compatibility"));
|
|
}
|
|
const html = await get("/", { "user-agent": "ClaudeBot/1.0", accept: "text/html" });
|
|
assert.equal(html.status, 200);
|
|
assert.match(html.headers.get("content-type"), /^text\/html/);
|
|
await html.body?.cancel();
|
|
});
|
|
|
|
test("homepage and existing social images still render", async () => {
|
|
const home = await get("/");
|
|
assert.equal(home.status, 200);
|
|
const html = await home.text();
|
|
assert.ok(html.includes('href="/docs/quickstart"'));
|
|
assert.ok(html.includes("832040"));
|
|
assert.doesNotMatch(html, /~320KB|~620KB|about 4ms|~35ms/);
|
|
for (const path of ["/og", "/og/limitations"]) {
|
|
const image = await get(path);
|
|
assert.equal(image.status, 200, path);
|
|
assert.match(image.headers.get("content-type"), /^image\/png/);
|
|
await image.body?.cancel();
|
|
}
|
|
});
|