Files
ObsiGate/tests/frontend/desktop.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

148 lines
6.4 KiB
JavaScript

#!/usr/bin/env node
/**
* ObsiGate — Desktop bridge (desktop.js) unit tests.
*
* Tests the pure logic of the Tauri bridge: env detection, invoke degradation,
* OS theme sync gating, and the first-run wizard decision. No real Tauri needed.
*
* Usage: node tests/frontend/desktop.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></body></html>`,
{ url: "http://localhost/", pretendToBeVisual: true }
);
const w = dom.window;
globalThis.window = w;
globalThis.document = w.document;
globalThis.localStorage = w.localStorage;
const moduleUrl = pathToFileURL(path.join(REPO_ROOT, "frontend", "js", "desktop.js")).href;
const desktop = await import(moduleUrl);
let passed = 0;
function ok(name, cond) {
assert.ok(cond, name);
passed++;
console.log(" ✓ " + name);
}
// ── Detection ──────────────────────────────────────────────────────────────
console.log("detection");
ok("isTauriEnv false when no __TAURI__", desktop.isTauriEnv() === false);
window.__TAURI__ = { core: { invoke: () => {} } };
ok("isTauriEnv true when __TAURI__ present", desktop.isTauriEnv() === true);
// ── invoke degradation ──────────────────────────────────────────────────────
console.log("invoke");
{
// Web env: invoke resolves undefined, never throws
delete window.__TAURI__;
const r = await desktop.invoke("get_version");
ok("invoke resolves undefined on web", r === undefined);
}
{
// Tauri env with working core.invoke
window.__TAURI__ = { core: { invoke: async (cmd) => "called:" + cmd } };
const r = await desktop.invoke("get_version");
ok("invoke forwards to core.invoke", r === "called:get_version");
}
{
// Tauri env but invoke throws → resolves undefined, no crash
window.__TAURI__ = { core: { invoke: async () => { throw new Error("boom"); } } };
const r = await desktop.invoke("get_version");
ok("invoke swallows IPC errors", r === undefined);
}
// ── getSystemTheme ──────────────────────────────────────────────────────────
console.log("getSystemTheme");
{
window.__TAURI__ = { core: { invoke: async () => "dark" } };
ok("getSystemTheme returns Tauri theme", (await desktop.getSystemTheme()) === "dark");
window.__TAURI__ = { core: { invoke: async () => undefined } };
const t = await desktop.getSystemTheme();
ok("getSystemTheme falls back to light/dark", t === "light" || t === "dark");
}
// ── hasExplicitThemeMode / syncSystemTheme ──────────────────────────────────
console.log("theme gating");
localStorage.clear();
ok("no explicit mode initially", desktop.hasExplicitThemeMode() === false);
// Web env: syncSystemTheme is a no-op (does not touch localStorage)
delete window.__TAURI__;
await desktop.syncSystemTheme();
ok("syncSystemTheme no-op on web", localStorage.getItem("obsigate-theme-mode") === null);
// Tauri env + no explicit mode → follows OS theme
window.__TAURI__ = { core: { invoke: async () => "dark" } };
await desktop.syncSystemTheme();
ok("syncSystemTheme sets OS mode on desktop first-run", localStorage.getItem("obsigate-theme-mode") === "dark");
// Tauri env + explicit mode → never overrides
localStorage.setItem("obsigate-theme-mode", "light");
window.__TAURI__ = { core: { invoke: async () => "dark" } };
await desktop.syncSystemTheme();
ok("syncSystemTheme respects explicit mode", localStorage.getItem("obsigate-theme-mode") === "light");
// High-contrast / sepia are never overridden even with no explicit flag check
localStorage.removeItem("obsigate-theme-mode");
localStorage.setItem("obsigate-theme-mode", "high-contrast");
await desktop.syncSystemTheme();
ok("syncSystemTheme never overrides high-contrast", localStorage.getItem("obsigate-theme-mode") === "high-contrast");
// ── Wizard decision ─────────────────────────────────────────────────────────
console.log("wizard");
localStorage.clear();
window.__TAURI__ = { core: { invoke: async () => "dark" } };
ok("shouldShowWizard: desktop, not dismissed, no vault → true",
desktop.shouldShowWizard({ isDesktop: true, dismissed: false, hasVaultPath: false }) === true);
ok("shouldShowWizard: dismissed → false",
desktop.shouldShowWizard({ isDesktop: true, dismissed: true, hasVaultPath: false }) === false);
ok("shouldShowWizard: has vault path → false",
desktop.shouldShowWizard({ isDesktop: true, dismissed: false, hasVaultPath: true }) === false);
ok("shouldShowWizard: web → false",
desktop.shouldShowWizard({ isDesktop: false, dismissed: false, hasVaultPath: false }) === false);
// dismissWizard persists the flag
desktop.dismissWizard();
ok("dismissWizard persists flag", localStorage.getItem(desktop.getWizardDismissedKey()) === "1");
ok("shouldShowWizard reflects dismissed flag via localStorage",
desktop.shouldShowWizard({ isDesktop: true, hasVaultPath: false }) === false);
// ── crash banner ────────────────────────────────────────────────────────────
console.log("crash banner");
{
delete window.__TAURI__;
desktop.defineBackendCrashBanner();
ok("defines window.showBackendCrashBanner", typeof window.showBackendCrashBanner === "function");
window.showBackendCrashBanner();
ok("crash banner injects DOM node", document.getElementById("backend-crash-banner") !== null);
window.showBackendCrashBanner(); // idempotent — no duplicate
ok("crash banner idempotent", document.querySelectorAll("#backend-crash-banner").length === 1);
}
console.log("\n✅ " + passed + " tests passés");