mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-16 07:35:27 +02:00
perf: load document libraries only when needed
This commit is contained in:
@@ -21,3 +21,5 @@ scripts
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
Dockerfile*
|
Dockerfile*
|
||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
|
|
||||||
|
/tmp
|
||||||
|
|||||||
@@ -122,3 +122,12 @@ Run `npm run build:ui` after changing a template or frontend script. Use
|
|||||||
configured upstream. `npm run test:ui` rebuilds the assets and runs the frontend
|
configured upstream. `npm run test:ui` rebuilds the assets and runs the frontend
|
||||||
regression and DOM interaction tests. `npm run build` also builds the UI for
|
regression and DOM interaction tests. `npm run build` also builds the UI for
|
||||||
production.
|
production.
|
||||||
|
|
||||||
|
The initial bundle contains the Vue app. Markdown extensions load on content
|
||||||
|
routes; PDF.js, Ace, and notebook support load when their viewers mount.
|
||||||
|
Org support loads in the repository explorer, and Mermaid loads only when a
|
||||||
|
diagram is encountered. These libraries use hashed URLs and load once per tab.
|
||||||
|
|
||||||
|
The Docker build compiles the same bundles and copies them, the asset manifest,
|
||||||
|
and document-worker assets into the runtime image. The Compose app serves these
|
||||||
|
assets directly; no frontend development server is needed.
|
||||||
|
|||||||
+15
-9
@@ -23,16 +23,17 @@ const coreJsFiles = [
|
|||||||
"public/script/utils.js",
|
"public/script/utils.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
const vendorJsFiles = [
|
const markdownFiles = [
|
||||||
"public/script/external/pdf.js",
|
|
||||||
"public/script/external/katex.min.js",
|
"public/script/external/katex.min.js",
|
||||||
"public/script/external/katex-auto-render.min.js",
|
"public/script/external/katex-auto-render.min.js",
|
||||||
"public/script/external/marked-katex-extension.umd.min.js",
|
"public/script/external/marked-katex-extension.umd.min.js",
|
||||||
"public/script/external/marked-mermaid.js",
|
"public/script/external/marked-mermaid.js",
|
||||||
"public/script/external/notebook.min.js",
|
];
|
||||||
"public/script/external/org.js",
|
const pdfFiles = ["public/script/external/pdf.js"];
|
||||||
"public/script/external/ace/ace.js",
|
const notebookFiles = ["public/script/external/notebook.min.js"];
|
||||||
];
|
const orgFiles = ["public/script/external/org.js"];
|
||||||
|
const editorFiles = ["public/script/external/ace/ace.js"];
|
||||||
|
const lazyGroups = { markdown: markdownFiles, pdf: pdfFiles, notebook: notebookFiles, org: orgFiles, editor: editorFiles };
|
||||||
|
|
||||||
const mermaidFiles = [
|
const mermaidFiles = [
|
||||||
"public/script/external/mermaid.min.js",
|
"public/script/external/mermaid.min.js",
|
||||||
@@ -64,10 +65,15 @@ function buildCoreJs(cb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildVendorJs() {
|
async function buildVendorJs() {
|
||||||
|
const lazyAssets = {};
|
||||||
|
await Promise.all(Object.entries(lazyGroups).map(async ([name, files]) => {
|
||||||
|
await promisify(pipeline)(orderedSrc(files), concat(`${name}.min.js`), uglify(), dest("public/script"));
|
||||||
|
lazyAssets[name] = `/script/${name}.${hashFile(`public/script/${name}.min.js`)}.min.js`;
|
||||||
|
}));
|
||||||
const app = await esbuild.build({
|
const app = await esbuild.build({
|
||||||
entryPoints: ["public/script/main.js"], bundle: true, write: false,
|
entryPoints: ["public/script/main.js"], bundle: true, write: false,
|
||||||
format: "iife", minify: true, target: "es2020",
|
format: "iife", minify: true, target: "es2020",
|
||||||
define: { "process.env.NODE_ENV": JSON.stringify("production"), __VUE_OPTIONS_API__: "true", __VUE_PROD_DEVTOOLS__: "false", __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false" },
|
define: { __LAZY_ASSETS__: JSON.stringify(lazyAssets), "process.env.NODE_ENV": JSON.stringify("production"), __VUE_OPTIONS_API__: "true", __VUE_PROD_DEVTOOLS__: "false", __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false" },
|
||||||
plugins: [{ name: "vue-templates", setup(build) {
|
plugins: [{ name: "vue-templates", setup(build) {
|
||||||
build.onLoad({ filter: /\.htm$/ }, async ({ path }) => {
|
build.onLoad({ filter: /\.htm$/ }, async ({ path }) => {
|
||||||
const { code, errors } = compileTemplate({
|
const { code, errors } = compileTemplate({
|
||||||
@@ -81,8 +87,7 @@ async function buildVendorJs() {
|
|||||||
});
|
});
|
||||||
} }],
|
} }],
|
||||||
});
|
});
|
||||||
await promisify(pipeline)(orderedSrc(vendorJsFiles), concat("vendor.min.js"), uglify(), dest("public/script"));
|
fs.writeFileSync("public/script/vendor.min.js", app.outputFiles[0].text);
|
||||||
fs.appendFileSync("public/script/vendor.min.js", "\n;" + app.outputFiles[0].text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildMermaidJs(cb) {
|
function buildMermaidJs(cb) {
|
||||||
@@ -100,6 +105,7 @@ function writeManifest(cb) {
|
|||||||
"mermaid.min.js": "public/script/mermaid.min.js",
|
"mermaid.min.js": "public/script/mermaid.min.js",
|
||||||
"all.min.css": "public/css/all.min.css",
|
"all.min.css": "public/css/all.min.css",
|
||||||
};
|
};
|
||||||
|
for (const name of Object.keys(lazyGroups)) files[`${name}.min.js`] = `public/script/${name}.min.js`;
|
||||||
const manifest = {};
|
const manifest = {};
|
||||||
for (const [key, filePath] of Object.entries(files)) {
|
for (const [key, filePath] of Object.entries(files)) {
|
||||||
const hash = hashFile(filePath);
|
const hash = hashFile(filePath);
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"core.min.js": "core.c5bd53363a.min.js",
|
"core.min.js": "core.c5bd53363a.min.js",
|
||||||
"vendor.min.js": "vendor.abcdded7b8.min.js",
|
"vendor.min.js": "vendor.af902bc3b4.min.js",
|
||||||
"mermaid.min.js": "mermaid.f848a72d16.min.js",
|
"mermaid.min.js": "mermaid.f848a72d16.min.js",
|
||||||
"all.min.css": "all.076c089579.min.css"
|
"all.min.css": "all.076c089579.min.css",
|
||||||
|
"markdown.min.js": "markdown.ad7b1d71c3.min.js",
|
||||||
|
"pdf.min.js": "pdf.eaa7573247.min.js",
|
||||||
|
"notebook.min.js": "notebook.8844e2735f.min.js",
|
||||||
|
"org.min.js": "org.f4e2a3f59f.min.js",
|
||||||
|
"editor.min.js": "editor.e243722d87.min.js"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { h, ref, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
|
import { loadLibrary, loadEditor } from "./lazy-assets.js";
|
||||||
|
import { h, ref, watch, onMounted, onBeforeUnmount, nextTick, defineAsyncComponent } from "vue";
|
||||||
import HtmlDoc from "./html-doc.js";
|
import HtmlDoc from "./html-doc.js";
|
||||||
import PdfViewer from "./pdf-viewer.js";
|
import PdfViewer from "./pdf-viewer.js";
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ const Notebook = {
|
|||||||
request = new AbortController();
|
request = new AbortController();
|
||||||
try {
|
try {
|
||||||
const json = props.content ? JSON.parse(props.content) : await fetch(props.file?.download_url || props.file, { signal: request.signal }).then(r => { if (!r.ok) throw Error("Notebook request failed"); return r.json(); });
|
const json = props.content ? JSON.parse(props.content) : await fetch(props.file?.download_url || props.file, { signal: request.signal }).then(r => { if (!r.ok) throw Error("Notebook request failed"); return r.json(); });
|
||||||
|
await loadLibrary("notebook");
|
||||||
if (current !== generation) return;
|
if (current !== generation) return;
|
||||||
host.value.innerHTML = DOMPurify.sanitize(nb.parse(json).render());
|
host.value.innerHTML = DOMPurify.sanitize(nb.parse(json).render());
|
||||||
host.value.querySelectorAll("pre code").forEach(el => window.Prism?.highlightElement(el));
|
host.value.querySelectorAll("pre code").forEach(el => window.Prism?.highlightElement(el));
|
||||||
@@ -64,21 +66,35 @@ const Loc = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export const components = { Markdown, GistFile, Notebook, Loc, HtmlDoc, Pdfviewer: PdfViewer };
|
export const components = { Markdown, GistFile, Notebook, Loc, HtmlDoc, Pdfviewer: defineAsyncComponent(async () => {
|
||||||
|
await loadLibrary("pdf");
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = "/script/external/pdf.worker.js";
|
||||||
|
return PdfViewer;
|
||||||
|
}) };
|
||||||
|
|
||||||
export const codeEditor = {
|
export const codeEditor = {
|
||||||
mounted(el, { value }) {
|
async mounted(el, { value }) {
|
||||||
const editor = ace.edit(el);
|
el._editorValue = value;
|
||||||
el._editor = editor;
|
try {
|
||||||
editor.setValue(String(value.content ?? ""), -1);
|
await loadEditor();
|
||||||
applyEditorOptions(el, value.options);
|
if (el._editorDisposed) return;
|
||||||
value.options?.onLoad?.(editor);
|
const latest = el._editorValue;
|
||||||
|
const editor = ace.edit(el);
|
||||||
|
el._editor = editor;
|
||||||
|
editor.setValue(String(latest.content ?? ""), -1);
|
||||||
|
applyEditorOptions(el, latest.options);
|
||||||
|
latest.options?.onLoad?.(editor);
|
||||||
|
} catch (error) {
|
||||||
|
if (!el._editorDisposed) el.textContent = error.message;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updated(el, { value }) {
|
updated(el, { value }) {
|
||||||
|
el._editorValue = value;
|
||||||
|
if (!el._editor) return;
|
||||||
if (el._editor.getValue() !== String(value.content ?? "")) el._editor.setValue(String(value.content ?? ""), -1);
|
if (el._editor.getValue() !== String(value.content ?? "")) el._editor.setValue(String(value.content ?? ""), -1);
|
||||||
applyEditorOptions(el, value.options);
|
applyEditorOptions(el, value.options);
|
||||||
},
|
},
|
||||||
beforeUnmount(el) { el._editor.destroy(); },
|
beforeUnmount(el) { el._editorDisposed = true; el._editor?.destroy(); },
|
||||||
};
|
};
|
||||||
function applyEditorOptions(el, options = {}) {
|
function applyEditorOptions(el, options = {}) {
|
||||||
if (options.mode) el._editor.session.setMode("ace/mode/" + options.mode);
|
if (options.mode) el._editor.session.setMode("ace/mode/" + options.mode);
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,24 @@
|
|||||||
|
const pending = new Map();
|
||||||
|
// Generated from the completed library bundles, so deployments invalidate caches.
|
||||||
|
const assets = __LAZY_ASSETS__;
|
||||||
|
export function loadLibrary(name) {
|
||||||
|
if (!pending.has(name)) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = assets[name];
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = () => {
|
||||||
|
pending.delete(name);
|
||||||
|
script.remove();
|
||||||
|
reject(new Error(`Unable to load ${name}. Please retry.`));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
pending.set(name, promise);
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
return pending.get(name);
|
||||||
|
}
|
||||||
|
export async function loadEditor() {
|
||||||
|
await loadLibrary("editor");
|
||||||
|
ace.config.set("basePath", "/script/external/ace/");
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { loadLibrary } from "./lazy-assets.js";
|
||||||
import { createApp, h, watch, provide, inject, onBeforeUnmount } from "vue";
|
import { createApp, h, watch, provide, inject, onBeforeUnmount } from "vue";
|
||||||
import { createRouter, createWebHistory, RouterView } from "vue-router";
|
import { createRouter, createWebHistory, RouterView } from "vue-router";
|
||||||
import { pageRoutes } from "./routes.js";
|
import { pageRoutes } from "./routes.js";
|
||||||
@@ -116,7 +117,13 @@ export function mountApplication(target = "#app", options = {}) {
|
|||||||
app.directive("code-editor", codeEditor);
|
app.directive("code-editor", codeEditor);
|
||||||
app.directive("paper-scrollspy", paperScrollspy);
|
app.directive("paper-scrollspy", paperScrollspy);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
router.beforeEach(() => { root?.emit("routeLeave"); });
|
router.beforeEach(async to => {
|
||||||
|
root?.emit("routeLeave");
|
||||||
|
if (/^\/(r|repository|anonymize|pull-request-anonymize|gist-anonymize|pr|gist)(\/|$)/.test(to.path)) {
|
||||||
|
await loadLibrary("markdown");
|
||||||
|
}
|
||||||
|
if (/^\/(r|repository)\//.test(to.path)) await loadLibrary("org");
|
||||||
|
});
|
||||||
router.afterEach(to => {
|
router.afterEach(to => {
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
root.title = to.meta.title;
|
root.title = to.meta.title;
|
||||||
@@ -129,6 +136,4 @@ export function mountApplication(target = "#app", options = {}) {
|
|||||||
return { app, router, state: root };
|
return { app, router, state: root };
|
||||||
}
|
}
|
||||||
|
|
||||||
ace.config.set("basePath", "/script/external/ace/");
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "/script/external/pdf.worker.js";
|
|
||||||
if (document.querySelector("#app")) window.anonymousApp = mountApplication();
|
if (document.querySelector("#app")) window.anonymousApp = mountApplication();
|
||||||
|
|||||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+20
-22
File diff suppressed because one or more lines are too long
@@ -47,15 +47,18 @@ describe("asset build", function () {
|
|||||||
it("preserves script dependencies and CSS precedence and hashes completed assets", function () {
|
it("preserves script dependencies and CSS precedence and hashes completed assets", function () {
|
||||||
const result = build();
|
const result = build();
|
||||||
expect(result.status, result.stderr).to.equal(0);
|
expect(result.status, result.stderr).to.equal(0);
|
||||||
for (const [bundle, group] of [["core", "coreJsFiles"], ["vendor", "vendorJsFiles"], ["mermaid", "mermaidFiles"]]) {
|
for (const [bundle, group] of [["core", "coreJsFiles"], ["markdown", "markdownFiles"], ["pdf", "pdfFiles"], ["editor", "editorFiles"], ["notebook", "notebookFiles"], ["org", "orgFiles"], ["mermaid", "mermaidFiles"]]) {
|
||||||
const context = { assetOrder: [] };
|
const context = { assetOrder: [] };
|
||||||
vm.runInNewContext(fs.readFileSync(path.join(directory, `public/script/${bundle}.min.js`), "utf8"), context);
|
vm.runInNewContext(fs.readFileSync(path.join(directory, `public/script/${bundle}.min.js`), "utf8"), context);
|
||||||
expect(context.assetOrder).to.deep.equal(groups[group]);
|
expect(context.assetOrder).to.deep.equal(groups[group]);
|
||||||
if (bundle === "vendor") expect(context.appLoaded).to.equal(true);
|
|
||||||
}
|
}
|
||||||
expect(fs.readFileSync(path.join(directory, "public/css/all.min.css"), "utf8")).to.equal(".cascade{color:#00f}".repeat(groups.cssFiles.length - 1) + ".cascade{color:red}");
|
expect(fs.readFileSync(path.join(directory, "public/css/all.min.css"), "utf8")).to.equal(".cascade{color:#00f}".repeat(groups.cssFiles.length - 1) + ".cascade{color:red}");
|
||||||
const manifest = JSON.parse(fs.readFileSync(path.join(directory, "public/asset-manifest.json"), "utf8"));
|
const manifest = JSON.parse(fs.readFileSync(path.join(directory, "public/asset-manifest.json"), "utf8"));
|
||||||
expect(Object.keys(manifest)).to.have.length(4);
|
expect(Object.keys(manifest)).to.have.length(9);
|
||||||
|
const appContext = {};
|
||||||
|
vm.runInNewContext(fs.readFileSync(path.join(directory, "public/script/vendor.min.js"), "utf8"), appContext);
|
||||||
|
expect(appContext.appLoaded).to.equal(true);
|
||||||
for (const [name, hashed] of Object.entries(manifest)) {
|
for (const [name, hashed] of Object.entries(manifest)) {
|
||||||
const content = fs.readFileSync(path.join(directory, "public", name.endsWith(".css") ? "css" : "script", name));
|
const content = fs.readFileSync(path.join(directory, "public", name.endsWith(".css") ? "css" : "script", name));
|
||||||
const hash = require("node:crypto").createHash("md5").update(content).digest("hex").slice(0, 10);
|
const hash = require("node:crypto").createHash("md5").update(content).digest("hex").slice(0, 10);
|
||||||
@@ -72,7 +75,7 @@ describe("asset build", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("fails on invalid JavaScript without publishing a manifest", function () {
|
it("fails on invalid JavaScript without publishing a manifest", function () {
|
||||||
fs.writeFileSync(path.join(directory, groups.vendorJsFiles[0]), "function {");
|
fs.writeFileSync(path.join(directory, groups.pdfFiles[0]), "function {");
|
||||||
const result = build();
|
const result = build();
|
||||||
expect(result.status).not.to.equal(0);
|
expect(result.status).not.to.equal(0);
|
||||||
expect(result.stderr).to.include("uglify");
|
expect(result.stderr).to.include("uglify");
|
||||||
|
|||||||
+28
-4
@@ -1,5 +1,5 @@
|
|||||||
const { expect } = require("chai");
|
const { expect } = require("chai");
|
||||||
const { JSDOM, VirtualConsole } = require("jsdom");
|
const { JSDOM, VirtualConsole, ResourceLoader } = require("jsdom");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { URL } = require("node:url");
|
const { URL } = require("node:url");
|
||||||
@@ -9,13 +9,22 @@ const publicDir = path.join(__dirname, "../public");
|
|||||||
const bundles = ["core.min.js", "vendor.min.js"].map(name => fs.readFileSync(path.join(publicDir, "script", name), "utf8"));
|
const bundles = ["core.min.js", "vendor.min.js"].map(name => fs.readFileSync(path.join(publicDir, "script", name), "utf8"));
|
||||||
|
|
||||||
async function browser(route = "/", overrides = {}) {
|
async function browser(route = "/", overrides = {}) {
|
||||||
const errors = [], requests = [];
|
const errors = [], requests = [], assets = [];
|
||||||
const virtualConsole = new VirtualConsole();
|
const virtualConsole = new VirtualConsole();
|
||||||
virtualConsole.on("jsdomError", error => {
|
virtualConsole.on("jsdomError", error => {
|
||||||
if (!error.message.includes("navigation (except hash changes)")) errors.push(error.message);
|
if (!error.message.includes("navigation (except hash changes)")) errors.push(error.message);
|
||||||
});
|
});
|
||||||
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
|
virtualConsole.on("error", error => errors.push(error?.message || String(error)));
|
||||||
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
|
const dom = new JSDOM('<!doctype html><html><head></head><body><div id="app"></div></body></html>', {
|
||||||
|
resources: new class extends ResourceLoader {
|
||||||
|
fetch(url) {
|
||||||
|
assets.push(new URL(url).pathname);
|
||||||
|
if (/\/pdf\.[a-f0-9]+\.min\.js$/.test(new URL(url).pathname) && dom.window.pdfjsLib) return Promise.resolve(Buffer.from(""));
|
||||||
|
const pathname = new URL(url).pathname.replace(/\.[a-f0-9]{10}\.min\.js$/, ".min.js");
|
||||||
|
if (pathname.startsWith("/script/")) return Promise.resolve(fs.readFileSync(path.join(publicDir, pathname)));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}(),
|
||||||
url: "http://localhost" + route, runScripts: "dangerously", pretendToBeVisual: true, virtualConsole,
|
url: "http://localhost" + route, runScripts: "dangerously", pretendToBeVisual: true, virtualConsole,
|
||||||
});
|
});
|
||||||
const window = dom.window;
|
const window = dom.window;
|
||||||
@@ -46,7 +55,7 @@ async function browser(route = "/", overrides = {}) {
|
|||||||
await app.router.isReady();
|
await app.router.isReady();
|
||||||
await delay(30);
|
await delay(30);
|
||||||
return {
|
return {
|
||||||
window, app, errors, requests,
|
window, app, errors, requests, assets,
|
||||||
async go(path) { await app.router.push(path); await delay(30); },
|
async go(path) { await app.router.push(path); await delay(30); },
|
||||||
async input(selector, value, event = "input") {
|
async input(selector, value, event = "input") {
|
||||||
const input = window.document.querySelector(selector);
|
const input = window.document.querySelector(selector);
|
||||||
@@ -79,6 +88,21 @@ describe("Vue 3 UI", function () {
|
|||||||
expect(ui.errors).to.deep.equal([]);
|
expect(ui.errors).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads document libraries on demand and reuses them across navigation", async function () {
|
||||||
|
ui = await browser("/dashboard");
|
||||||
|
expect(ui.assets.filter(url => url.endsWith(".js"))).to.deep.equal([]);
|
||||||
|
await ui.go("/r/test/README.md");
|
||||||
|
expect(ui.assets.some(url => /\/markdown\./.test(url))).to.equal(true);
|
||||||
|
expect(ui.assets.some(url => /\/(pdf|editor|notebook)\./.test(url))).to.equal(false);
|
||||||
|
await ui.go("/faq");
|
||||||
|
await ui.go("/r/test/README.md");
|
||||||
|
expect(ui.assets.filter(url => /\/markdown\./.test(url))).to.have.length(1);
|
||||||
|
await ui.go("/r/test/hello.js");
|
||||||
|
expect(ui.assets.some(url => /\/editor\./.test(url))).to.equal(true);
|
||||||
|
expect(ui.window.document.querySelector(".ace_editor")).not.to.equal(null);
|
||||||
|
expect(ui.errors).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("validates a claim, binds input values and renders server validation failures", async function () {
|
it("validates a claim, binds input values and renders server validation failures", async function () {
|
||||||
ui = await browser("/claim", { "/api/repo/claim": { __status: 404, body: {} } });
|
ui = await browser("/claim", { "/api/repo/claim": { __status: 404, body: {} } });
|
||||||
const form = ui.window.document.querySelector("form");
|
const form = ui.window.document.querySelector("form");
|
||||||
@@ -231,7 +255,7 @@ describe("Vue 3 UI", function () {
|
|||||||
it("loads PDF pages, changes documents and releases the previous document", async function () {
|
it("loads PDF pages, changes documents and releases the previous document", async function () {
|
||||||
ui = await browser("/faq");
|
ui = await browser("/faq");
|
||||||
const loaded = [], destroyed = [];
|
const loaded = [], destroyed = [];
|
||||||
ui.window.pdfjsLib = { getDocument({ url }) {
|
ui.window.pdfjsLib = { GlobalWorkerOptions: {}, getDocument({ url }) {
|
||||||
loaded.push(url);
|
loaded.push(url);
|
||||||
return { promise: Promise.resolve({
|
return { promise: Promise.resolve({
|
||||||
numPages: 2,
|
numPages: 2,
|
||||||
|
|||||||
Reference in New Issue
Block a user