123 lines
5.5 KiB
TypeScript
123 lines
5.5 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { readFileSync, readdirSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { renderComponent } from "../../test/src/index.ts";
|
|
import { viewerPlugin } from "../src/plugin.ts";
|
|
import { compileWrnFile } from "../../compiler/src/index.ts";
|
|
import {
|
|
mergePluginAssets,
|
|
readPluginAsset,
|
|
runtimeScriptsForMarkup,
|
|
} from "../../dev-server/src/plugin-assets.ts";
|
|
import type { ClientRuntimeDefinition } from "@wrnexus/plugin";
|
|
|
|
for (const filename of readdirSync(new URL("../components", import.meta.url))) {
|
|
test(`${filename} renders its presentation contract`, async () => {
|
|
const source = readFileSync(new URL(`../components/${filename}`, import.meta.url), "utf8");
|
|
const html = await renderComponent(source, { class: "custom-presentation" });
|
|
expect(html).toContain("custom-presentation");
|
|
expect(html).toContain(filename === "Viewer.wrn" ? "data-wrnexus-runtime" : "data-viewer-");
|
|
});
|
|
}
|
|
|
|
test("Viewer renders escaped source attributes and runtime marker", async () => {
|
|
const source = readFileSync(new URL("../components/Viewer.wrn", import.meta.url), "utf8");
|
|
const html = await renderComponent(source, {
|
|
name: "<script>alert(1)</script>",
|
|
src: '/file?x="',
|
|
});
|
|
expect(html).toContain('data-wrnexus-runtime="viewer"');
|
|
expect(html).not.toContain("<script>alert(1)</script>");
|
|
expect(html).toContain('data-component="ViewerShell"');
|
|
});
|
|
|
|
test("framework bundles editor separately and injects only the viewer runtime", async () => {
|
|
const runtimes = viewerPlugin().clientRuntimes as ClientRuntimeDefinition[];
|
|
const assets = mergePluginAssets(runtimes, []);
|
|
const viewer = assets.find((asset) => asset.id === "runtime:viewer")!;
|
|
const editor = assets.find((asset) => asset.id === "runtime:viewer-code")!;
|
|
const decode = (body: string | ArrayBuffer) =>
|
|
typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
const main = decode(await readPluginAsset(viewer));
|
|
expect(main).not.toContain("class EditorView");
|
|
expect(main).toContain("/__wrnexus/assets/viewer-code.js");
|
|
expect(decode(await readPluginAsset(editor))).toContain("mountBuilderSourceEditor");
|
|
expect(
|
|
runtimeScriptsForMarkup('<section data-wrnexus-runtime="viewer"></section>', runtimes),
|
|
).toHaveLength(1);
|
|
});
|
|
|
|
test("example page compiles with explicit Viewer import", () => {
|
|
const path = new URL("../../../examples/basic-app/app/pages/file-viewer.wrn", import.meta.url);
|
|
expect(compileWrnFile(readFileSync(path, "utf8"), path.pathname)).toContain("Viewer");
|
|
});
|
|
test("plugin contributes existing browser assets and mounts no conversion routes", () => {
|
|
const plugin = viewerPlugin();
|
|
expect(plugin.routeEntries).toBeUndefined();
|
|
expect(Array.isArray(plugin.componentDirs)).toBe(true);
|
|
for (const file of [
|
|
new URL("../src/runtime.ts", import.meta.url),
|
|
new URL("../viewer.css", import.meta.url),
|
|
])
|
|
expect(readFileSync(file, "utf8").length).toBeGreaterThan(0);
|
|
// The two core runtimes, plus one per installed optional parser (and the
|
|
// pdf.js worker). Only the core viewer runtime is ever injected into a page.
|
|
const ids = (plugin.clientRuntimes as ClientRuntimeDefinition[]).map((runtime) => runtime.id);
|
|
expect(ids.slice(0, 2)).toEqual(["viewer-code", "viewer"]);
|
|
for (const id of ids.slice(2)) expect(id.startsWith("viewer-")).toBe(true);
|
|
expect(plugin.styleSources).toHaveLength(1);
|
|
});
|
|
|
|
test("optional parsers are contributed only when their package resolves", () => {
|
|
const runtimes = viewerPlugin().clientRuntimes as ClientRuntimeDefinition[];
|
|
const ids = new Set(runtimes.map((runtime) => runtime.id));
|
|
const resolvable = (specifier: string) => {
|
|
try {
|
|
createRequire(new URL("../src/plugin.ts", import.meta.url)).resolve(specifier);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
for (const [id, specifier] of [
|
|
["viewer-sheet", "xlsx"],
|
|
["viewer-docx", "mammoth"],
|
|
["viewer-zip", "fflate"],
|
|
["viewer-pdfjs", "pdfjs-dist"],
|
|
] as const)
|
|
expect(ids.has(id)).toBe(resolvable(specifier));
|
|
// pdf.js is useless without its worker asset.
|
|
expect(ids.has("viewer-pdfjs-worker")).toBe(ids.has("viewer-pdfjs"));
|
|
});
|
|
|
|
test("optional runtimes are served but never injected into a page", () => {
|
|
const runtimes = viewerPlugin().clientRuntimes as ClientRuntimeDefinition[];
|
|
const markup = '<section data-wrnexus-runtime="viewer"></section>';
|
|
expect(runtimeScriptsForMarkup(markup, runtimes)).toHaveLength(1);
|
|
const assets = mergePluginAssets(runtimes, []);
|
|
for (const id of ["viewer-sheet", "viewer-zip", "viewer-docx", "viewer-pdfjs"]) {
|
|
if (!runtimes.some((runtime) => runtime.id === id)) continue;
|
|
const asset = assets.find((item) => item.id === `runtime:${id}`)!;
|
|
expect(asset.publicPath).toBe(`/__wrnexus/assets/${id}.js`);
|
|
}
|
|
});
|
|
|
|
test("each optional runtime bundles for the browser and exports its parser", async () => {
|
|
const runtimes = viewerPlugin().clientRuntimes as ClientRuntimeDefinition[];
|
|
const assets = mergePluginAssets(runtimes, []);
|
|
const decode = (body: string | ArrayBuffer) =>
|
|
typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
for (const [id, exported] of [
|
|
["viewer-sheet", "readWorkbook"],
|
|
["viewer-docx", "docxToHtml"],
|
|
["viewer-zip", "listZip"],
|
|
["viewer-pdfjs", "renderPdf"],
|
|
] as const) {
|
|
const asset = assets.find((item) => item.id === `runtime:${id}`);
|
|
if (!asset) continue;
|
|
const code = decode(await readPluginAsset(asset));
|
|
expect(code.length).toBeGreaterThan(0);
|
|
expect(code).toContain(exported);
|
|
}
|
|
}, 120_000);
|