CI / lint (push) Successful in 1m11s
CI / security (push) Successful in 43s
CI / test (push) Successful in 2m22s
CI / build (push) Successful in 1m41s
CI / e2e (push) Successful in 10m50s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
- Signature des mises a jour Tauri : paire de cles minisign generee, cle publique dans tauri.conf.json, createUpdaterArtifacts actif, secrets CI exposes (repli build non signe si secret absent). - Wizard 1er lancement : etat persistant wizard_done cote Rust (get_wizard_state/complete_wizard) pour ne plus reafficher la banniere, retro-compatible. - Protocole des 6 tests E2E manuels : docs/DESKTOP_E2E_CHECKLIST.md. - CI : desktop.test.mjs ajoute au job lint. - Docs : ROADMAP, fiche desktop-tauri, CHANGELOG, README desktop, guide releases. - fix(build): retirer les libs WeasyPrint inutiles du stage builder Docker.
167 lines
7.2 KiB
JavaScript
167 lines
7.2 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
|
|
localStorage.clear();
|
|
await 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);
|
|
|
|
// dismissWizard also persists on the Rust side (survives localStorage clear)
|
|
{
|
|
let called = null;
|
|
window.__TAURI__ = { core: { invoke: async (cmd) => { called = cmd; return undefined; } } };
|
|
localStorage.clear();
|
|
await desktop.dismissWizard();
|
|
ok("dismissWizard invokes complete_wizard", called === "complete_wizard");
|
|
}
|
|
|
|
// initDesktopWizard must NOT inject the banner when Rust reports wizard done
|
|
{
|
|
localStorage.clear();
|
|
window.__TAURI__ = { core: { invoke: async (cmd) => (cmd === "get_wizard_state" ? true : undefined) } };
|
|
await desktop.initDesktopWizard();
|
|
ok("initDesktopWizard skips banner when wizard_done is true",
|
|
document.getElementById("obsigate-desktop-wizard") === null);
|
|
}
|
|
|
|
// ── 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");
|