diff --git a/dashboard/src/v2/hooks/use-settings-page-state.ts b/dashboard/src/v2/hooks/use-settings-page-state.ts index c7583831c5..e6bd74842d 100644 --- a/dashboard/src/v2/hooks/use-settings-page-state.ts +++ b/dashboard/src/v2/hooks/use-settings-page-state.ts @@ -41,6 +41,10 @@ import { providerDescriptions, providerLabels, } from "../lib/onboarding-provider-settings.js"; +import { + clearAppearancePreview, + publishAppearancePreview, +} from "../lib/appearance-preview.js"; import type { InvocationRoutingId, ProviderConfigId, @@ -431,23 +435,11 @@ export const useSettingsPageState = ( const activeCategoryConfig = categories.find((category) => category.id === activeCategory) ?? categories[0]!; useEffect(() => { - if (typeof window === "undefined") { - return; - } - window.dispatchEvent(new CustomEvent("codeux:appearance-preview", { - detail: { appearance: editableSettings?.appearance ?? null }, - })); + publishAppearancePreview(editableSettings?.appearance ?? null); }, [editableSettings?.appearance]); useEffect(() => { - if (typeof window === "undefined") { - return; - } - return () => { - window.dispatchEvent(new CustomEvent("codeux:appearance-preview", { - detail: { appearance: null }, - })); - }; + return clearAppearancePreview; }, []); const normalizedSearch = settingsSearch.trim().toLowerCase(); diff --git a/dashboard/src/v2/lib/__tests__/appearance-preview.test.ts b/dashboard/src/v2/lib/__tests__/appearance-preview.test.ts new file mode 100644 index 0000000000..46173ced42 --- /dev/null +++ b/dashboard/src/v2/lib/__tests__/appearance-preview.test.ts @@ -0,0 +1,58 @@ +// @vitest-environment happy-dom +import { afterEach, describe, expect, it, vi } from "vitest"; +import { DEFAULT_DASHBOARD_SETTINGS } from "../../../lib/settings.js"; +import { + APPEARANCE_PREVIEW_EVENT, + clearAppearancePreview, + publishAppearancePreview, +} from "../appearance-preview.js"; + +describe("appearance preview helpers", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("publishes a non-null appearance payload", () => { + const previews: unknown[] = []; + const appearance = { + ...DEFAULT_DASHBOARD_SETTINGS.appearance, + backgroundMode: "STATIC" as const, + staticBackgroundColor: "#123456", + }; + const listener = (event: Event) => { + previews.push((event as CustomEvent).detail); + }; + + window.addEventListener(APPEARANCE_PREVIEW_EVENT, listener); + try { + publishAppearancePreview(appearance); + + expect(previews).toEqual([{ appearance }]); + } finally { + window.removeEventListener(APPEARANCE_PREVIEW_EVENT, listener); + } + }); + + it("publishes null to clear the preview", () => { + const previews: unknown[] = []; + const listener = (event: Event) => { + previews.push((event as CustomEvent).detail); + }; + + window.addEventListener(APPEARANCE_PREVIEW_EVENT, listener); + try { + publishAppearancePreview(null); + + expect(previews).toEqual([{ appearance: null }]); + } finally { + window.removeEventListener(APPEARANCE_PREVIEW_EVENT, listener); + } + }); + + it("does nothing when window is unavailable", () => { + vi.stubGlobal("window", undefined); + + expect(() => publishAppearancePreview(DEFAULT_DASHBOARD_SETTINGS.appearance)).not.toThrow(); + expect(() => clearAppearancePreview()).not.toThrow(); + }); +}); diff --git a/dashboard/src/v2/lib/appearance-preview.ts b/dashboard/src/v2/lib/appearance-preview.ts new file mode 100644 index 0000000000..e6da2972aa --- /dev/null +++ b/dashboard/src/v2/lib/appearance-preview.ts @@ -0,0 +1,21 @@ +import type { DashboardSettings } from "../../types.js"; + +export const APPEARANCE_PREVIEW_EVENT = "codeux:appearance-preview"; + +export interface AppearancePreviewDetail { + appearance: DashboardSettings["appearance"] | null; +} + +export function publishAppearancePreview(appearance: DashboardSettings["appearance"] | null): void { + if (typeof window === "undefined") { + return; + } + + window.dispatchEvent(new CustomEvent(APPEARANCE_PREVIEW_EVENT, { + detail: { appearance }, + })); +} + +export function clearAppearancePreview(): void { + publishAppearancePreview(null); +}