import { describe, expect, it } from "vitest"; import { QueryClient } from "@tanstack/react-query"; import { APP_SETTINGS_KEY, DEFAULT_APP_SETTINGS, DEFAULT_CLIENT_SETTINGS, loadAppSettingsFromStorage, loadSettingsFromStorage, parseTerminalScrollbackLines, saveAppSettings, type SettingsDeps, } from "./storage"; import { createFakeDesktopBridge, createInMemoryKeyValueStorage } from "./fakes"; const LEGACY_SETTINGS_KEY = "@paseo:settings"; function makeDeps( overrides: { storage?: ReturnType; desktop?: ReturnType; } = {}, ): SettingsDeps & { storage: ReturnType; desktop: ReturnType; } { return { storage: overrides.storage ?? createInMemoryKeyValueStorage(), desktop: overrides.desktop ?? createFakeDesktopBridge(), }; } describe("loadAppSettingsFromStorage", () => { it("defaults theme to auto when storage is empty", async () => { const deps = makeDeps(); const result = await loadAppSettingsFromStorage(deps); expect(result.theme).toBe("auto"); }); it("seeds storage with the client defaults when nothing is persisted", async () => { const deps = makeDeps(); const result = await loadAppSettingsFromStorage(deps); expect(result).toEqual(DEFAULT_CLIENT_SETTINGS); expect(deps.storage.entries.get(APP_SETTINGS_KEY)).toBe( JSON.stringify(DEFAULT_CLIENT_SETTINGS), ); }); it("loads configured terminal scrollback lines from app settings", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ terminalScrollbackLines: 42_000 }), }), }); const result = await loadAppSettingsFromStorage(deps); expect(result.terminalScrollbackLines).toBe(42_000); }); it("normalizes terminal scrollback lines from storage", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ terminalScrollbackLines: 1_000_000.9 }), }), }); const result = await loadAppSettingsFromStorage(deps); expect(result.terminalScrollbackLines).toBe(1_000_000); }); it("migrates the legacy theme key into the new settings object", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [LEGACY_SETTINGS_KEY]: JSON.stringify({ theme: "dark", manageBuiltInDaemon: false, releaseChannel: "beta", }), }), }); const result = await loadAppSettingsFromStorage(deps); expect(result).toEqual({ theme: "dark", sendBehavior: "interrupt", serviceUrlBehavior: "ask", terminalScrollbackLines: 10_000, }); expect(deps.storage.entries.get(APP_SETTINGS_KEY)).toBe(JSON.stringify(result)); }); }); describe("loadSettingsFromStorage", () => { it("defaults built-in daemon management to enabled when storage is empty", async () => { const deps = makeDeps(); const result = await loadSettingsFromStorage(deps); expect(result).toEqual(DEFAULT_APP_SETTINGS); }); it("defaults release channel to stable when storage is empty", async () => { const deps = makeDeps(); const result = await loadSettingsFromStorage(deps); expect(result.releaseChannel).toBe("stable"); }); it("ignores renderer-owned daemon management state outside Electron", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ theme: "light", manageBuiltInDaemon: false, }), }), }); const result = await loadSettingsFromStorage(deps); expect(result).toEqual({ theme: "light", manageBuiltInDaemon: true, sendBehavior: "interrupt", serviceUrlBehavior: "ask", terminalScrollbackLines: 10_000, releaseChannel: "stable", }); }); it("ignores renderer-owned release channel outside Electron", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ releaseChannel: "beta" }), }), }); const result = await loadSettingsFromStorage(deps); expect(result.releaseChannel).toBe("stable"); }); it("migrates legacy desktop-owned settings through the bridge before reading effective settings", async () => { const desktop = createFakeDesktopBridge({ isElectron: true, settings: { releaseChannel: "beta", daemon: { manageBuiltInDaemon: false, keepRunningAfterQuit: true }, }, }); const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ theme: "light", manageBuiltInDaemon: false, releaseChannel: "beta", }), }), desktop, }); const result = await loadSettingsFromStorage(deps); expect(desktop.migrationsApplied).toEqual([ { manageBuiltInDaemon: false, releaseChannel: "beta" }, ]); expect(result).toEqual({ theme: "light", sendBehavior: "interrupt", serviceUrlBehavior: "ask", terminalScrollbackLines: 10_000, manageBuiltInDaemon: false, releaseChannel: "beta", }); }); it("does not call the desktop bridge outside Electron", async () => { const desktop = createFakeDesktopBridge({ isElectron: false }); const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify({ theme: "light" }), }), desktop, }); const result = await loadSettingsFromStorage(deps); expect(desktop.migrationsApplied).toEqual([]); expect(result).toEqual({ theme: "light", sendBehavior: "interrupt", serviceUrlBehavior: "ask", terminalScrollbackLines: 10_000, manageBuiltInDaemon: true, releaseChannel: "stable", }); }); }); describe("saveAppSettings", () => { it("saves terminal scrollback through app settings persistence", async () => { const deps = makeDeps({ storage: createInMemoryKeyValueStorage({ [APP_SETTINGS_KEY]: JSON.stringify(DEFAULT_CLIENT_SETTINGS), }), }); const queryClient = new QueryClient(); await saveAppSettings({ queryClient, updates: { terminalScrollbackLines: 42_000 }, deps, }); expect(deps.storage.entries.get(APP_SETTINGS_KEY)).toBe( JSON.stringify({ ...DEFAULT_CLIENT_SETTINGS, terminalScrollbackLines: 42_000, }), ); }); }); describe("parseTerminalScrollbackLines", () => { it("clamps negative values to the minimum and rejects non-numeric strings", () => { expect(parseTerminalScrollbackLines("-10")).toBe(0); expect(parseTerminalScrollbackLines("abc")).toBeNull(); }); });