#!/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( ``, { 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");