Files
ObsiGate/tests/frontend/excalidraw-viewer.test.mjs
T
bruno ac16fc1f0e
CI / lint (push) Successful in 52s
CI / security (push) Successful in 36s
CI / test (push) Successful in 1m6s
CI / build (push) Successful in 1m15s
CI / e2e (push) Failing after 12m40s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
feat: #78 Excalidraw finitions + #67 push + #68 health-detailed + #77 desktop jumplist
#78 Excalidraw — finitions B5/C8/F2/F3
- backend/indexer.py: port Python pur de lz-string decompressFromBase64 (bitsPerChar=6, resetValue=32) validé contre 4 fixtures JS truth (texte accentué, edge cases) — remplace le stub base64 non-fonctionnel
- B5 indexation: .excalidraw.md (format plugin Obsidian) maintenant décompressé côté Python → texte indexé pour recherche TF-IDF
- 7 nouveaux tests B5 dans tests/test_excalidraw.py (13/13 total)
- F2: excalidraw-viewer.test.mjs enregistré dans CI (lint job)
- F3: tests/e2e/excalidraw.spec.js (9 specs — ouverture .excalidraw/.excalidraw.md, création via modale/menu contextuel, toolbar, thème, pop-out, recherche)
- Fixtures test_vault/diagram.excalidraw + diagram.excalidraw.md

#67 Push notifications (Web Push API + VAPID)
- backend/push.py: router + VAPID keys + send_push_notification (pywebpush>=2.3.0)
- frontend/js/push.js: subscription UI + service worker integration
- tests/test_push.py: 10 tests (subscribe/list/unsubscribe/vapid key)
- requirements.txt + sw.js + locales push strings

#68 Health check enrichi
- backend/main.py: GET /api/health/detailed (admin) — memory/cpu/disk/backups/index/SSE connections
- backend/indexer.py: _last_full_index_ts tracking
- tests/conftest.py: admin_client fixture
- tests/test_api_main.py: 3 nouveaux tests health detailed

#77 Desktop jumplist
- desktop/src/jumplist.rs: Windows jumplist integration
- Cargo.toml/lock + capabilities + main.rs
- frontend/js/desktop.js: Tauri bridge (isTauriEnv, invoke, getSystemTheme, syncSystemTheme, shouldShowWizard, crash banner)
- tests/frontend/desktop.test.mjs: 21 tests JSDOM (détection, invoke degradation, theme gating, wizard, crash banner)
- tests/frontend/unit.test.mjs: desktop.js whitelisted (standalone global reader)

# Frontend & CI
- frontend/sw.js: réécriture complète (precache + push event handlers + offline)
- frontend/index.html: section push dans settings + about repositionné
- frontend/js/app.js: initDesktopIntegration + initPush
- CI .gitea/workflows/ci.yml: excalidraw-viewer.test.mjs ajouté au lint job

All checks: ruff clean, 552 backend tests pass, 9 frontend unit, 5 excalidraw-viewer, 21 desktop, 9 pane-manager, validate-imports 33 modules.
2026-09-09 23:18:29 -04:00

142 lines
5.3 KiB
JavaScript

#!/usr/bin/env node
/**
* ObsiGate — JSDOM integration tests for Excalidraw Viewer (ROADMAP #78 F2).
*
* Loads the actual excalidraw-viewer.js ES module in a JSDOM environment and
* verifies iframe creation, the postMessage init flow, and export helpers.
*
* Usage: node tests/frontend/excalidraw-viewer.test.mjs
*/
import { strict as assert } from "node:assert";
import { JSDOM } from "jsdom";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, "..", "..");
// ── JSDOM bootstrap ─────────────────────────────────────────────────────────
const dom = new JSDOM(
`<!DOCTYPE html>
<html>
<body>
<div id="content-area"></div>
</body>
</html>`,
{ url: "http://localhost/", pretendToBeVisual: true }
);
const w = dom.window;
globalThis.window = w;
globalThis.document = w.document;
globalThis.HTMLElement = w.HTMLElement;
globalThis.Element = w.Element;
globalThis.Node = w.Node;
globalThis.Event = w.Event;
globalThis.CustomEvent = w.CustomEvent;
globalThis.MessageEvent = w.MessageEvent;
globalThis.localStorage = w.localStorage;
globalThis.sessionStorage = w.sessionStorage;
Object.defineProperty(globalThis, "navigator", {
value: w.navigator,
configurable: true,
writable: true,
});
// The viewer calls addEventListener on the iframe contentWindow's source check.
// We stub window.postMessage (jsdom doesn't dispatch it).
w.HTMLIFrameElement.prototype.postMessage = () => {};
// jsdom may not initialize iframe.sandbox for created elements; we define it
// lazily so the viewer's sandbox.add() calls work.
Object.defineProperty(w.HTMLIFrameElement.prototype, "sandbox", {
configurable: true,
get() {
if (!this._sandboxList) {
this._sandboxList = new Set();
const list = this._sandboxList;
this._sandboxListAdd = (token) => {
list.add(token);
};
}
return {
add: (token) => this._sandboxListAdd(token),
contains: (token) => this._sandboxList.has(token),
};
},
});
// ── Helpers ─────────────────────────────────────────────────────────────────
let testCount = 0;
let passCount = 0;
async function test(name, fn) {
testCount++;
try {
await fn();
console.log(` ✓ ${name}`);
passCount++;
} catch (e) {
console.log(` ✗ ${name}`);
console.log(` ${e.message}`);
if (e.stack) console.log(` ${e.stack.split("\n").slice(1, 3).join("\n ")}`);
}
}
// ── Dynamic import (after globals are set) ─────────────────────────────────
const mod = await import(
pathToFileURL(path.join(REPO_ROOT, "frontend", "js", "excalidraw-viewer.js")).href
);
const { renderExcalidraw, notifyExcalidrawThemeChange, destroyExcalidrawEditor } = mod;
// ── Test suite ──────────────────────────────────────────────────────────────
console.log("\n── excalidraw-viewer.js JSDOM integration tests (#78 F2) ──\n");
await test("module exports renderExcalidraw + helpers", () => {
assert.equal(typeof renderExcalidraw, "function");
assert.equal(typeof notifyExcalidrawThemeChange, "function");
assert.equal(typeof destroyExcalidrawEditor, "function");
});
await test("renderExcalidraw creates an iframe with sandbox + static src", () => {
const container = document.getElementById("content-area");
const data = {
is_excalidraw: true,
vault: "TestVault",
path: "diagram.excalidraw",
excalidraw_data: { elements: [], appState: {}, files: {} },
};
renderExcalidraw(container, data, "TestVault", "diagram.excalidraw");
const iframe = container.querySelector("iframe");
assert.ok(iframe, "iframe should be created");
assert.ok(iframe.src.includes("/static/excalidraw-editor.html"), `src: ${iframe.src}`);
assert.ok(iframe.sandbox.contains("allow-scripts"), "sandbox allow-scripts");
assert.ok(iframe.sandbox.contains("allow-same-origin"), "sandbox allow-same-origin");
assert.match(iframe.style.cssText, /100%/);
});
await test("renderExcalidraw accepts compressed .excalidraw.md data", () => {
const container = document.getElementById("content-area");
const data = {
is_excalidraw: true,
vault: "V",
path: "diagram.excalidraw.md",
excalidraw_data_compressed: "dummy",
};
// Should not throw when building with compressed payload
renderExcalidraw(container, data, "V", "diagram.excalidraw.md");
const iframe = container.querySelector("iframe");
assert.ok(iframe, "iframe created for .excalidraw.md");
});
await test("notifyExcalidrawThemeChange does not throw with no editors", () => {
notifyExcalidrawThemeChange("dark");
});
await test("destroyExcalidrawEditor is idempotent (no-op for unknown id)", () => {
destroyExcalidrawEditor("excalidraw-999");
});
console.log(`\n${passCount}/${testCount} excalidraw-viewer tests passed\n`);
process.exit(passCount === testCount ? 0 : 1);