| 1 | import assert from "node:assert/strict"; |
| 2 | import React, { act } from "react"; |
| 3 | import { createRoot } from "react-dom/client"; |
| 4 | import { JSDOM } from "jsdom"; |
| 5 | import { useDesktopPreferences } from "../app-runtime/useDesktopPreferences"; |
| 6 | import { getSessionExperience, hydrateSessionExperience } from "../lib/sessionExperience"; |
| 7 | import { LocaleProvider } from "../lib/i18n"; |
| 8 | import type { DesktopStartupSettingsView } from "../lib/types"; |
| 9 | import { installDesktopHostStub } from "./desktopHostStub"; |
| 10 | |
| 11 | const dom = new JSDOM("<div id='root'></div>", { url: "http://localhost", pretendToBeVisual: true }); |
| 12 | Object.assign(globalThis, { window: dom.window, document: dom.window.document, localStorage: dom.window.localStorage, |
| 13 | CustomEvent: dom.window.CustomEvent, IS_REACT_ACT_ENVIRONMENT: true }); |
| 14 | window.matchMedia = (() => ({ matches: false, addEventListener() {}, removeEventListener() {} })) as typeof window.matchMedia; |
| 15 | let requests = 0; |
| 16 | let fullSettings = 0; |
| 17 | let resolveStartup!: (settings: DesktopStartupSettingsView) => void; |
| 18 | const startup = new Promise<DesktopStartupSettingsView>(resolve => { resolveStartup = resolve; }); |
| 19 | const appStubTable = { |
| 20 | DesktopStartupSettings: () => { requests++; return startup; }, |
| 21 | Settings: () => { fullSettings++; throw new Error("startup must not request full Settings"); }, |
| 22 | BotRuntimeStatus: async () => null, |
| 23 | SetTrayLocale: async () => {}, |
| 24 | GetThemeExperience: async () => ({ themeMode: "light", baseStyle: "graphite", effectiveStyle: "graphite" }), |
| 25 | }; |
| 26 | const desktopStub = installDesktopHostStub(appStubTable); |
| 27 | let current!: ReturnType<typeof useDesktopPreferences>; |
| 28 | function Probe() { current = useDesktopPreferences(); return <div>{current.configLoadWarnings.join("|")}</div>; } |
| 29 | const root = createRoot(document.getElementById("root")!); |
| 30 | const snapshot = { sessionExperience: "deep", desktopTheme: "light", desktopThemeStyle: "graphite", |
| 31 | desktopLanguage: "en", checkUpdates: true, updaterEnabled: true, configWarnings: ["warning"], configWarningsRevision: 3 } as DesktopStartupSettingsView; |
| 32 | try { |
| 33 | localStorage.setItem("reasonix-process-fold", "auto"); |
| 34 | await act(async () => root.render(<LocaleProvider><Probe /></LocaleProvider>)); |
| 35 | assert.equal(requests, 1); |
| 36 | await act(async () => { resolveStartup(snapshot); await import("../lib/themeExperience"); }); |
| 37 | assert.equal(getSessionExperience(), "deep", "backend wins over an old localStorage mirror"); |
| 38 | assert.equal(current.startupUpdateChecksEnabled, true, "stable build and enabled preference allow automatic checks"); |
| 39 | assert.deepEqual(current.configLoadWarnings, ["warning"]); |
| 40 | await act(async () => { desktopStub.emit("config:load-warnings", ["stale"], 2); }); |
| 41 | assert.deepEqual(current.configLoadWarnings, ["warning"], "stale runtime warning cannot replace startup snapshot"); |
| 42 | await act(async () => { desktopStub.emit("config:load-warnings", ["current"], 4); }); |
| 43 | assert.deepEqual(current.configLoadWarnings, ["current"]); |
| 44 | await act(async () => { await current.reload({ ...snapshot, sessionExperience: undefined }); }); |
| 45 | assert.equal(getSessionExperience(), "standard", "old backend missing field resolves standard"); |
| 46 | await act(async () => { await current.reload({ ...snapshot, sessionExperience: undefined, updaterEnabled: undefined }); }); |
| 47 | assert.equal(current.startupUpdateChecksEnabled, false, "old backend missing updater capability fails closed"); |
| 48 | await act(async () => { await current.reload({ ...snapshot, sessionExperience: undefined, updaterEnabled: false }); }); |
| 49 | assert.equal(current.startupUpdateChecksEnabled, false, "disabled build capability overrides the user preference"); |
| 50 | assert.equal(fullSettings, 0, "preferences and IM projection never request full Settings"); |
| 51 | const oldReload = current.reload; |
| 52 | await act(async () => root.unmount()); |
| 53 | assert.equal(desktopStub.events.get("config:load-warnings")?.size ?? 0, 0); |
| 54 | await oldReload(snapshot); |
| 55 | assert.equal(getSessionExperience(), "standard", "disposed commands cannot mutate global experience"); |
| 56 | const originalWarn = console.warn; |
| 57 | console.warn = () => {}; |
| 58 | const failedRoot = createRoot(document.getElementById("root")!); |
| 59 | try { |
| 60 | Object.assign(appStubTable, { DesktopStartupSettings: async () => { throw new Error("offline"); } }); |
| 61 | hydrateSessionExperience("deep"); |
| 62 | await act(async () => failedRoot.render(<LocaleProvider><Probe /></LocaleProvider>)); |
| 63 | await act(async () => { await current.reload(); }); |
| 64 | assert.equal(getSessionExperience(), "standard", "failed first snapshot uses canonical standard, not a legacy local preference"); |
| 65 | assert.equal(current.startupUpdateChecksEnabled, false, "startup RPC failure disables automatic checks"); |
| 66 | } finally { await act(async () => failedRoot.unmount()); console.warn = originalWarn; } |
| 67 | console.log("desktop preferences: lightweight snapshot, legacy mirror, warning revision and disposal passed"); |
| 68 | } finally { dom.window.close(); } |
| 69 |