From 58f53a421b67bfe75cd0a4912773c56c105fce82 Mon Sep 17 00:00:00 2001 From: Hikari Date: Tue, 3 Mar 2026 14:45:22 -0800 Subject: [PATCH] test: add coverage for configStore methods and derived stores --- src/lib/stores/config.test.ts | 292 +++++++++++++++++++++++++++++++++- 1 file changed, 291 insertions(+), 1 deletion(-) diff --git a/src/lib/stores/config.test.ts b/src/lib/stores/config.test.ts index acb6904..7453b59 100644 --- a/src/lib/stores/config.test.ts +++ b/src/lib/stores/config.test.ts @@ -1,4 +1,5 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { get } from "svelte/store"; import { configStore, maskPaths, @@ -7,6 +8,11 @@ import { applyTheme, applyCustomThemeColors, clearCustomThemeColors, + isDarkTheme, + isStreamerMode, + isCompactMode, + shouldHidePaths, + showThinkingBlocks, MIN_FONT_SIZE, MAX_FONT_SIZE, DEFAULT_FONT_SIZE, @@ -843,4 +849,288 @@ describe("config store", () => { expect(lastSaved.font_size).toBe(16); }); }); + + describe("loadConfig error path", () => { + it("resets to default config and logs error when loadConfig fails", async () => { + vi.mocked(invoke).mockResolvedValue(null); + await configStore.updateConfig({ theme: "light" }); + + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + vi.mocked(invoke).mockRejectedValue(new Error("Backend unavailable")); + + await configStore.loadConfig(); + + expect(configStore.getConfig().theme).toBe("dark"); + expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to load config:", expect.any(Error)); + consoleErrorSpy.mockRestore(); + }); + }); + + describe("configStore sidebar methods", () => { + it("openSidebar sets isSidebarOpen to true", () => { + configStore.closeSidebar(); + configStore.openSidebar(); + expect(get(configStore.isSidebarOpen)).toBe(true); + }); + + it("closeSidebar sets isSidebarOpen to false", () => { + configStore.openSidebar(); + configStore.closeSidebar(); + expect(get(configStore.isSidebarOpen)).toBe(false); + }); + + it("toggleSidebar switches from false to true", () => { + configStore.closeSidebar(); + configStore.toggleSidebar(); + expect(get(configStore.isSidebarOpen)).toBe(true); + }); + + it("toggleSidebar switches from true to false", () => { + configStore.openSidebar(); + configStore.toggleSidebar(); + expect(get(configStore.isSidebarOpen)).toBe(false); + }); + }); + + describe("configStore setTheme method", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("setTheme updates the theme via invoke", async () => { + await configStore.setTheme("light"); + expect(configStore.getConfig().theme).toBe("light"); + }); + + it("setTheme with custom colors updates custom_theme_colors", async () => { + const colors: CustomThemeColors = { + bg_primary: "#001122", + bg_secondary: null, + bg_terminal: null, + accent_primary: null, + accent_secondary: null, + text_primary: null, + text_secondary: null, + border_color: null, + }; + + await configStore.setTheme("custom", colors); + + expect(configStore.getConfig().theme).toBe("custom"); + expect(configStore.getConfig().custom_theme_colors.bg_primary).toBe("#001122"); + }); + }); + + describe("configStore setCustomThemeColors with custom theme active", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + await configStore.setTheme("custom"); + }); + + afterEach(() => { + vi.resetAllMocks(); + clearCustomThemeColors(); + }); + + it("applies custom colors to DOM when current theme is custom", async () => { + const colors: CustomThemeColors = { + bg_primary: "#aabbcc", + bg_secondary: null, + bg_terminal: null, + accent_primary: null, + accent_secondary: null, + text_primary: null, + text_secondary: null, + border_color: null, + }; + + await configStore.setCustomThemeColors(colors); + + expect(document.documentElement.style.getPropertyValue("--bg-primary")).toBe("#aabbcc"); + }); + }); + + describe("configStore font size methods", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + await configStore.updateConfig({ font_size: DEFAULT_FONT_SIZE }); + vi.resetAllMocks(); + vi.mocked(invoke).mockResolvedValue(null); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("setFontSize updates to the given value", async () => { + await configStore.setFontSize(18); + expect(configStore.getConfig().font_size).toBe(18); + }); + + it("setFontSize clamps to minimum", async () => { + await configStore.setFontSize(1); + expect(configStore.getConfig().font_size).toBe(MIN_FONT_SIZE); + }); + + it("setFontSize clamps to maximum", async () => { + await configStore.setFontSize(99); + expect(configStore.getConfig().font_size).toBe(MAX_FONT_SIZE); + }); + + it("increaseFontSize increases font size by 2", async () => { + await configStore.increaseFontSize(); + expect(configStore.getConfig().font_size).toBe(DEFAULT_FONT_SIZE + 2); + }); + + it("increaseFontSize does not exceed maximum", async () => { + await configStore.setFontSize(MAX_FONT_SIZE); + await configStore.increaseFontSize(); + expect(configStore.getConfig().font_size).toBe(MAX_FONT_SIZE); + }); + + it("decreaseFontSize decreases font size by 2", async () => { + await configStore.decreaseFontSize(); + expect(configStore.getConfig().font_size).toBe(DEFAULT_FONT_SIZE - 2); + }); + + it("decreaseFontSize does not go below minimum", async () => { + await configStore.setFontSize(MIN_FONT_SIZE); + await configStore.decreaseFontSize(); + expect(configStore.getConfig().font_size).toBe(MIN_FONT_SIZE); + }); + + it("resetFontSize restores the default font size", async () => { + await configStore.setFontSize(20); + await configStore.resetFontSize(); + expect(configStore.getConfig().font_size).toBe(DEFAULT_FONT_SIZE); + }); + }); + + describe("configStore removeAutoGrantedTool", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + await configStore.updateConfig({ auto_granted_tools: [] }); + vi.resetAllMocks(); + vi.mocked(invoke).mockResolvedValue(null); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("removes an existing tool", async () => { + await configStore.addAutoGrantedTool("Bash"); + await configStore.removeAutoGrantedTool("Bash"); + expect(configStore.getConfig().auto_granted_tools).not.toContain("Bash"); + }); + + it("is a no-op when the tool is not in the list", async () => { + await configStore.removeAutoGrantedTool("NonExistentTool"); + expect(configStore.getConfig().auto_granted_tools).toEqual([]); + }); + }); + + describe("configStore toggle methods", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + await configStore.updateConfig({ streamer_mode: false, compact_mode: false }); + vi.resetAllMocks(); + vi.mocked(invoke).mockResolvedValue(null); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("toggleStreamerMode flips streamer_mode from false to true", async () => { + await configStore.toggleStreamerMode(); + expect(configStore.getConfig().streamer_mode).toBe(true); + }); + + it("toggleStreamerMode flips streamer_mode from true to false", async () => { + await configStore.updateConfig({ streamer_mode: true }); + await configStore.toggleStreamerMode(); + expect(configStore.getConfig().streamer_mode).toBe(false); + }); + + it("toggleCompactMode flips compact_mode from false to true", async () => { + await configStore.toggleCompactMode(); + expect(configStore.getConfig().compact_mode).toBe(true); + }); + + it("toggleCompactMode flips compact_mode from true to false", async () => { + await configStore.updateConfig({ compact_mode: true }); + await configStore.toggleCompactMode(); + expect(configStore.getConfig().compact_mode).toBe(false); + }); + + it("setCompactMode enables compact mode", async () => { + await configStore.setCompactMode(true); + expect(configStore.getConfig().compact_mode).toBe(true); + }); + + it("setCompactMode disables compact mode", async () => { + await configStore.updateConfig({ compact_mode: true }); + await configStore.setCompactMode(false); + expect(configStore.getConfig().compact_mode).toBe(false); + }); + }); + + describe("derived stores (live subscriptions)", () => { + beforeEach(async () => { + vi.mocked(invoke).mockResolvedValue(null); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("isDarkTheme is true when theme is dark", async () => { + await configStore.updateConfig({ theme: "dark" }); + expect(get(isDarkTheme)).toBe(true); + }); + + it("isDarkTheme is false when theme is not dark", async () => { + await configStore.updateConfig({ theme: "light" }); + expect(get(isDarkTheme)).toBe(false); + }); + + it("isStreamerMode reflects streamer_mode config", async () => { + await configStore.updateConfig({ streamer_mode: true }); + expect(get(isStreamerMode)).toBe(true); + await configStore.updateConfig({ streamer_mode: false }); + expect(get(isStreamerMode)).toBe(false); + }); + + it("isCompactMode reflects compact_mode config", async () => { + await configStore.updateConfig({ compact_mode: true }); + expect(get(isCompactMode)).toBe(true); + await configStore.updateConfig({ compact_mode: false }); + expect(get(isCompactMode)).toBe(false); + }); + + it("shouldHidePaths is true when both streamer flags are enabled", async () => { + await configStore.updateConfig({ streamer_mode: true, streamer_hide_paths: true }); + expect(get(shouldHidePaths)).toBe(true); + }); + + it("shouldHidePaths is false when streamer_mode is disabled", async () => { + await configStore.updateConfig({ streamer_mode: false, streamer_hide_paths: true }); + expect(get(shouldHidePaths)).toBe(false); + }); + + it("showThinkingBlocks is true when show_thinking_blocks is enabled", async () => { + await configStore.updateConfig({ show_thinking_blocks: true }); + expect(get(showThinkingBlocks)).toBe(true); + }); + + it("showThinkingBlocks is false when show_thinking_blocks is disabled", async () => { + await configStore.updateConfig({ show_thinking_blocks: false }); + expect(get(showThinkingBlocks)).toBe(false); + }); + }); });