fix: Editeur Excalidraw - diagramme vide (CSS + appState) et auto-save pendant l'edition (BUG-064, BUG-065)
This commit is contained in:
@@ -12,6 +12,7 @@ import { strict as assert } from "node:assert";
|
||||
import { JSDOM } from "jsdom";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -138,5 +139,95 @@ await test("destroyExcalidrawEditor is idempotent (no-op for unknown id)", () =>
|
||||
destroyExcalidrawEditor("excalidraw-999");
|
||||
});
|
||||
|
||||
// ── Static regression guards (BUG-064) ──────────────────────────────────────
|
||||
const editorHtml = fs.readFileSync(
|
||||
path.join(REPO_ROOT, "frontend", "excalidraw-editor.html"),
|
||||
"utf8"
|
||||
);
|
||||
const mainPy = fs.readFileSync(path.join(REPO_ROOT, "backend", "main.py"), "utf8");
|
||||
const styleCss = fs.readFileSync(path.join(REPO_ROOT, "frontend", "style.css"), "utf8");
|
||||
const viewerJs = fs.readFileSync(
|
||||
path.join(REPO_ROOT, "frontend", "js", "excalidraw-viewer.js"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
await test("excalidraw-editor.html loads the Excalidraw stylesheet", () => {
|
||||
// Without it the editor is unstyled AND the container enters a resize
|
||||
// feedback loop up to the 2^25 canvas cap → blank scene.
|
||||
assert.match(
|
||||
editorHtml,
|
||||
/https:\/\/esm\.sh\/@excalidraw\/excalidraw@[\d.]+\/dist\/prod\/index\.css/,
|
||||
"the Excalidraw CSS must be linked from esm.sh"
|
||||
);
|
||||
});
|
||||
|
||||
await test("backend CSP style-src allows esm.sh (Excalidraw stylesheet)", () => {
|
||||
const csp = mainPy.match(/style-src ([^";]+);/);
|
||||
assert.ok(csp, "CSP style-src directive not found");
|
||||
assert.ok(
|
||||
csp[1].includes("https://esm.sh"),
|
||||
`style-src must allow https://esm.sh (found: ${csp[1]})`
|
||||
);
|
||||
});
|
||||
|
||||
await test("excalidraw-editor.html sanitizes appState (collaborators + viewport geometry)", () => {
|
||||
assert.match(editorHtml, /function sanitizeAppState/, "sanitizeAppState helper missing");
|
||||
assert.match(editorHtml, /collaborators instanceof Map/, "collaborators must be restored as a Map");
|
||||
assert.match(
|
||||
editorHtml,
|
||||
/"width", "height", "offsetLeft", "offsetTop"/,
|
||||
"container-derived viewport geometry must be dropped"
|
||||
);
|
||||
});
|
||||
|
||||
await test("style.css — Excalidraw goes full width when the sidebar is hidden", () => {
|
||||
// Mirrors the PDF/image full-bleed rule: the centered 1200px reading column
|
||||
// must not apply to the Excalidraw viewer.
|
||||
const rule = styleCss.match(
|
||||
/\.content-area:has\(iframe\[src\*="excalidraw-editor\.html"\]\)\s*\{([^}]*)\}/
|
||||
);
|
||||
assert.ok(rule, "Excalidraw full-bleed rule not found");
|
||||
assert.match(rule[1], /max-width:\s*none/, "the 1200px reading cap must be lifted");
|
||||
assert.match(rule[1], /margin:\s*0/, "the centered margin must be removed");
|
||||
});
|
||||
|
||||
await test("excalidraw-viewer.js has no autosave (no requestSave)", () => {
|
||||
// Autosave writes the file, which emits `index_updated` and reloads the
|
||||
// viewer — a visible page refresh that also interrupts drawing. Saves are
|
||||
// explicit (in-editor Save button / Ctrl+S) only.
|
||||
assert.doesNotMatch(viewerJs, /requestSave/, "the viewer must not request automatic saves");
|
||||
assert.doesNotMatch(viewerJs, /saveTimer/, "no autosave debounce timer should remain");
|
||||
});
|
||||
|
||||
await test("excalidraw-viewer.js allows fullscreen in the iframe", () => {
|
||||
assert.match(
|
||||
viewerJs,
|
||||
/setAttribute\(\s*['"]allow['"]\s*,\s*['"]fullscreen['"]\s*\)/,
|
||||
"iframe must set allow=fullscreen"
|
||||
);
|
||||
assert.match(
|
||||
viewerJs,
|
||||
/setAttribute\(\s*['"]allowfullscreen['"]\s*,\s*['"]['"]\s*\)/,
|
||||
"iframe must set the allowfullscreen attribute"
|
||||
);
|
||||
});
|
||||
|
||||
await test("excalidraw-editor.html exposes a fullscreen button", () => {
|
||||
assert.match(editorHtml, /id="btn-fullscreen"/, "fullscreen button missing");
|
||||
assert.match(
|
||||
editorHtml,
|
||||
/documentElement\.requestFullscreen/,
|
||||
"the button must request fullscreen on the editor document"
|
||||
);
|
||||
});
|
||||
|
||||
await test("excalidraw-editor.html — toolbar flush right at 45%", () => {
|
||||
const rule = editorHtml.match(/#excalidraw-toolbar\s*\{([^}]*)\}/);
|
||||
assert.ok(rule, "#excalidraw-toolbar rule not found");
|
||||
assert.match(rule[1], /right:\s*0\b/, "toolbar must be flush against the right edge");
|
||||
assert.match(rule[1], /top:\s*45%/, "group must start at 45% of the height");
|
||||
assert.match(rule[1], /flex-direction:\s*column/, "buttons must stay stacked vertically");
|
||||
});
|
||||
|
||||
console.log(`\n${passCount}/${testCount} excalidraw-viewer tests passed\n`);
|
||||
process.exit(passCount === testCount ? 0 : 1);
|
||||
Reference in New Issue
Block a user