Files
edgeever/tests/note-print.integration.mjs
edgeever-updater[bot] 67cf5297de chore: update EdgeEver from v2.18.0
EdgeEver-Upstream-Commit: 4235f3f2f1f68ad94802612362d1daea6dabb3cb
2026-11-03 09:33:32 +00:00

140 lines
8.1 KiB
JavaScript

// Actual production-page regression for #767. Run after bun run build:web:
// node tests/note-print.integration.mjs
// Uses an isolated Electron window/profile; never opens the installed app.
import assert from "node:assert/strict";
import { createRequire } from "node:module";
import { readFile, mkdtemp, writeFile, rm } from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { dirname, join, resolve, extname } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const desktopRequire = createRequire(join(root, "apps/desktop/package.json"));
if (!process.versions.electron) {
const directory = await mkdtemp(join(tmpdir(), "edgeever-print-test-"));
try {
const env = { ...process.env };
delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(desktopRequire("electron"), [fileURLToPath(import.meta.url), directory], {
stdio: "inherit", env,
});
const timeout = setTimeout(() => child.kill(), 90_000);
const code = await new Promise((resolveExit, reject) => {
child.once("error", reject);
child.once("exit", resolveExit);
}).finally(() => clearTimeout(timeout));
assert.equal(code, 0, "Electron print regression must succeed");
const { getDocument } = await import("pdfjs-dist/legacy/build/pdf.mjs");
for (const name of ["long", "short", "mixed"]) {
const metrics = JSON.parse(await readFile(join(directory, `${name}.json`), "utf8"));
const task = getDocument({ data: new Uint8Array(await readFile(join(directory, `${name}.pdf`))) });
const pdf = await task.promise;
assert.equal(pdf.numPages, metrics.pages, `${name}: saved page count must match preview`);
let text = "";
for (let page = 1; page <= pdf.numPages; page++) {
const content = await (await pdf.getPage(page)).getTextContent();
text += content.items.map((item) => item.str ?? "").join(" ");
}
assert.ok(text.includes("END-OF-NOTE"), `${name}: PDF must contain final paragraph`);
console.log(`PASS ${name}: ${pdf.numPages} PDF pages, final paragraph present, empty paragraphs retain line height`);
await task.destroy();
}
} finally {
if (process.env.EDGE_EVER_KEEP_PRINT_TEST_OUTPUT) console.log(`Artifacts: ${directory}`);
else await rm(directory, { recursive: true, force: true });
}
} else {
const { app, BrowserWindow } = desktopRequire("electron");
const { PRINT_PDF_OPTIONS } = await import("../apps/desktop/src/main/print-pdf.mjs");
const directory = process.argv[2];
app.setPath("userData", join(directory, "profile"));
app.whenReady().then(async () => {
const mime = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".woff2": "font/woff2", ".svg": "image/svg+xml" };
const server = createServer(async (request, response) => {
const pathname = new URL(request.url, "http://localhost").pathname;
if (pathname === "/fixture") return response.end("<!doctype html><title>Print regression</title>");
const path = resolve(root, "apps/web/dist", `.${decodeURIComponent(pathname)}`);
if (!path.startsWith(join(root, "apps/web/dist/"))) { response.writeHead(403).end(); return; }
try {
response.setHeader("Content-Type", mime[extname(path)] ?? "application/octet-stream");
response.end(await readFile(path));
} catch { response.writeHead(404).end(); }
});
await new Promise((listen) => server.listen(0, "127.0.0.1", listen));
const origin = `http://127.0.0.1:${server.address().port}`;
const opener = new BrowserWindow({ show: false });
opener.webContents.setWindowOpenHandler(() => ({
action: "allow", overrideBrowserWindowOptions: { show: true, width: 1000, height: 720 },
}));
// Keep the last window from quitting Electron before all cases finish.
app.on("window-all-closed", () => {});
try {
await opener.loadURL(`${origin}/fixture`);
for (const [name, html] of Object.entries({
long: '<p>Before</p><p></p><p></p><p>After</p>' + Array.from({ length: 70 }, (_, i) => `<p>Paragraph ${i + 1}</p>`).join(""),
short: '<p>Before</p><p></p><p>After</p>',
mixed: '<p>Before</p><p></p><p>After</p><ul><li><p>List item</p><p></p></li></ul><img width="120" height="60" src="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%27120%27 height=%2760%27%3E%3Crect width=%27120%27 height=%2760%27 fill=%27gray%27/%3E%3C/svg%3E"><table><tbody>' + Array.from({ length: 80 }, (_, i) => `<tr><td><p>Row ${i + 1}</p></td></tr>`).join("") + '</tbody></table>',
})) {
const created = new Promise((resolveWindow) => opener.webContents.once("did-create-window", resolveWindow));
await opener.webContents.executeJavaScript(`window.preview = window.open('/note-print.html?token=regression'); void 0`);
const preview = await created;
await new Promise((loaded) => {
if (!preview.webContents.isLoading()) loaded();
else preview.webContents.once("did-finish-load", loaded);
});
const payload = {
type: "edgeever:note-print", token: "regression", title: "PDF regression", notebook: "", tags: [], updatedAt: "", language: "en",
html: `${html}<p>END-OF-NOTE</p>`,
labels: { close: "Close", error: "Error", hint: "", preparing: "Preparing", print: "Save", ready: "Ready" },
appearance: { palette: "native", cssVariables: {}, customCss: "", fontFamily: null, fontId: null, mermaidTheme: "zinc-light" },
};
await opener.webContents.executeJavaScript(`window.preview.postMessage(${JSON.stringify(payload)}, ${JSON.stringify(origin)})`);
const metrics = await preview.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const deadline = Date.now() + 30000;
const check = () => {
if (document.querySelector('#print-action').disabled) {
if (Date.now() > deadline) return reject(new Error('Preview timeout: ' + document.querySelector('#print-status').textContent));
return setTimeout(check, 50);
}
const empty = [...document.querySelectorAll('.edgeever-print-content p:empty')];
resolve({ pages: document.querySelectorAll('.pagedjs_page').length,
blankHeights: empty.map(p => p.getBoundingClientRect().height),
lineHeights: empty.map(p => parseFloat(getComputedStyle(p).lineHeight)) });
}; check();
})`);
assert.ok(metrics.blankHeights.length > 0);
metrics.blankHeights.forEach((height, i) => assert.ok(height >= metrics.lineHeights[i] - 1, `${name}: empty paragraph collapsed`));
assert.ok(name === "short" ? metrics.pages === 1 : metrics.pages > 1);
// Real wheel input checks the scroll container rather than scrollTo alone.
preview.focus();
preview.webContents.focus();
if (name !== "short") {
let scrolled = false;
// The OS/compositor can take a few frames to focus a newly opened window.
for (let attempt = 0; attempt < 20 && !scrolled; attempt++) {
preview.webContents.sendInputEvent({ type: "mouseMove", x: 400, y: 400 });
preview.webContents.sendInputEvent({ type: "mouseWheel", x: 400, y: 400, deltaX: 0, deltaY: -300 });
await new Promise((wait) => setTimeout(wait, 100));
scrolled = await preview.webContents.executeJavaScript("window.scrollY > 0");
}
assert.ok(scrolled, `${name}: wheel must scroll`);
}
await writeFile(join(directory, `${name}.json`), JSON.stringify(metrics));
await writeFile(join(directory, `${name}.pdf`), await preview.webContents.printToPDF(PRINT_PDF_OPTIONS));
await writeFile(join(directory, `${name}.png`), (await preview.webContents.capturePage()).toPNG());
preview.destroy();
}
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
opener.destroy();
server.close();
app.exit(process.exitCode ?? 0);
}
}).catch((error) => { console.error(error); app.exit(1); });
}