Ship file viewer, editor improvements, and private package releases
This commit is contained in:
1 parent
645914955c
commit
a4a31032b9
158 files changed
+10523
-137
No files matched your search
@@ -0,0 +1,122 @@
|
||||
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);
|
||||
Reference in new issue
Block a user