191 lines · 5.7 KB · TypeScript
RawDownload
import type { ReactElement } from "react";import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { reactHookHarness as hooks } from "../../test/reactHookHarness";import {  getThemeDefinition,  installCustomTheme,  invalidateCustomThemes,  parseThemeFile,  removeCustomTheme,  THEME_FILE_VERSION,  themeColorToHex,  updateCustomTheme,  type ThemeDefinition,} from "../../themePalette";import type { ThemeEditorSession } from "./themeEditorStore"; const state = vi.hoisted(() => ({  session: null as ThemeEditorSession | null,  closeThemeEditor: vi.fn(),  onStoreChange: vi.fn(),  subscriptions: new Set<() => void>(),  theme: {    theme: "system",    themeHalves: null,    setTheme: vi.fn(),    refreshTheme: vi.fn(),  },})); vi.mock("react", async (importOriginal) => {  const actual = await importOriginal<typeof import("react")>();  const { reactHookHarness } = await import("../../test/reactHookHarness");  return {    ...actual,    useCallback: reactHookHarness.useCallback,    useSyncExternalStore: (      subscribe: (listener: () => void) => () => void,      getSnapshot: () => unknown,    ) => {      const subscription = reactHookHarness.useRef<(() => void) | null>(null);      if (!subscription.current) {        subscription.current = subscribe(() => state.onStoreChange());        state.subscriptions.add(subscription.current);      }      return getSnapshot();    },  };}); vi.mock("react/compiler-runtime", async () => {  const { reactHookHarness } = await import("../../test/reactHookHarness");  return { c: reactHookHarness.useMemoCache };}); vi.mock("../../hooks/useTheme", () => ({ useTheme: () => state.theme }));vi.mock("./themeEditorStore", () => ({  useThemeEditorStore: (select: (store: typeof state) => unknown) => select(state),}));vi.mock("../ui/toast", () => ({  toastManager: { add: vi.fn() },  stackedThreadToast: (value: unknown) => value,})); import { ThemeEditorHost } from "./ThemeEditorHost"; function renderEditor() {  hooks.beginRender();  const host = ThemeEditorHost() as ReactElement<{    children: ReactElement<{      editingTheme: ThemeDefinition | null;      seedTheme: ThemeDefinition | null;    }>;  }> | null;  return host?.props.children.props ?? null;} describe("ThemeEditorHost", () => {  beforeEach(() => {    hooks.reset();    state.session = null;    state.onStoreChange.mockReset();    const storage = new Map<string, string>();    vi.stubGlobal("window", {      localStorage: {        getItem: (key: string) => storage.get(key) ?? null,        setItem: (key: string, value: string) => storage.set(key, value),      },      addEventListener: vi.fn(),      removeEventListener: vi.fn(),    });    invalidateCustomThemes();  });   afterEach(() => {    for (const unsubscribe of state.subscriptions) unsubscribe();    state.subscriptions.clear();    vi.unstubAllGlobals();    invalidateCustomThemes();  });   it.each(["editingTheme", "seedTheme"] as const)(    "reopens the same %s with its saved colors",    (field) => {      const theme = installCustomTheme(        parseThemeFile({          version: THEME_FILE_VERSION,          id: "saved-colors",          name: "Saved colors",          appearance: "dark",          colors: { accent: "#1f6e4a" },        }),      );      const session = {        id: 1,        editingThemeId: field === "editingTheme" ? theme.id : null,        seedThemeId: field === "seedTheme" ? theme.id : null,        seedName: null,        initialAppearance: "dark" as const,      };      state.session = session;      expect(themeColorToHex(renderEditor()?.[field]?.colors.accent ?? "")).toBe("#1f6e4a");       updateCustomTheme({ ...theme, colors: { ...theme.colors, accent: "#7241b8" } });      expect(themeColorToHex(getThemeDefinition(theme.id)?.colors.accent ?? "")).toBe("#7241b8");      state.session = null;      expect(renderEditor()).toBeNull();      state.session = { ...session, id: 2 };       expect(themeColorToHex(renderEditor()?.[field]?.colors.accent ?? "")).toBe("#7241b8");    },  );   it.each(["editingTheme", "seedTheme"] as const)(    "refreshes an open %s when the library changes",    (field) => {      const theme = installCustomTheme(        parseThemeFile({          version: THEME_FILE_VERSION,          id: "updated-theme",          name: "Updated theme",          appearance: "dark",          colors: { accent: "#1f6e4a" },        }),      );      state.session = {        id: 1,        editingThemeId: field === "editingTheme" ? theme.id : null,        seedThemeId: field === "seedTheme" ? theme.id : null,        seedName: null,        initialAppearance: "dark",      };      let editor = renderEditor();      state.onStoreChange.mockImplementation(() => {        editor = renderEditor();      });       updateCustomTheme({ ...theme, colors: { ...theme.colors, accent: "#7241b8" } });       expect(themeColorToHex(editor?.[field]?.colors.accent ?? "")).toBe("#7241b8");    },  );   it("does not keep editing a theme removed from the library", () => {    const theme = installCustomTheme(      parseThemeFile({        version: THEME_FILE_VERSION,        id: "removed-theme",        name: "Removed theme",        appearance: "dark",        colors: { accent: "#1f6e4a" },      }),    );    state.session = {      id: 1,      editingThemeId: theme.id,      seedThemeId: null,      seedName: null,      initialAppearance: "dark",    };    let editor = renderEditor();    expect(editor?.editingTheme?.id).toBe(theme.id);    state.onStoreChange.mockImplementation(() => {      editor = renderEditor();    });     removeCustomTheme(theme.id);     expect(editor?.editingTheme).toBeNull();  });});