UNPKG

theme-o-rama

Version:

A TypeScript library for dynamic theme management in react + shadcn + tailwind applications

213 lines (212 loc) 8.02 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { describe, it, expect, beforeEach, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import { renderHook, act } from "@testing-library/react"; import { SimpleThemeProvider, useSimpleTheme } from "./simple-theme-context"; import * as themeModule from "./theme"; // Mock the applyTheme function vi.mock("./theme", async () => { const actual = await vi.importActual("./theme"); return { ...actual, applyTheme: vi.fn(), }; }); describe("SimpleThemeProvider", () => { beforeEach(() => { vi.clearAllMocks(); }); it("should render children", () => { render(_jsx(SimpleThemeProvider, { children: _jsx("div", { "data-testid": "child", children: "Test Child" }) })); expect(screen.getByTestId("child")).toBeInTheDocument(); expect(screen.getByText("Test Child")).toBeInTheDocument(); }); it("should start with loading state and then finish loading", async () => { const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); // Initially loading expect(result.current.isLoading).toBe(true); expect(result.current.currentTheme).toBe(null); // Wait for loading to complete await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(result.current.error).toBe(null); }); it("should provide setTheme function", async () => { const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const testTheme = { name: "test", displayName: "Test Theme", schemaVersion: 1, colors: { background: "hsl(0 0% 100%)", foreground: "hsl(0 0% 0%)", }, }; await act(async () => { await result.current.setTheme(testTheme); }); expect(result.current.currentTheme).toEqual(testTheme); }); it("should call applyTheme when setting a theme", async () => { const applyThemeSpy = vi.spyOn(themeModule, "applyTheme"); const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const testTheme = { name: "test", displayName: "Test Theme", schemaVersion: 1, }; await act(async () => { await result.current.setTheme(testTheme); }); expect(applyThemeSpy).toHaveBeenCalledWith(testTheme, document.documentElement); }); it("should call onThemeChange callback when theme changes", async () => { const onThemeChange = vi.fn(); const { result } = renderHook(() => useSimpleTheme(), { wrapper: ({ children }) => (_jsx(SimpleThemeProvider, { onThemeChange: onThemeChange, children: children })), }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const testTheme = { name: "test", displayName: "Test Theme", schemaVersion: 1, }; await act(async () => { await result.current.setTheme(testTheme); }); expect(onThemeChange).toHaveBeenCalledWith(testTheme); }); it("should provide initializeTheme function", async () => { const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); expect(result.current.initializeTheme).toBeDefined(); expect(typeof result.current.initializeTheme).toBe("function"); }); it("should handle imageResolver prop", async () => { const imageResolver = vi.fn(async (themeName, imagePath) => { return `/resolved/${themeName}/${imagePath}`; }); const { result } = renderHook(() => useSimpleTheme(), { wrapper: ({ children }) => (_jsx(SimpleThemeProvider, { imageResolver: imageResolver, children: children })), }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const themeWithImage = { name: "test", displayName: "Test Theme", schemaVersion: 1, backgroundImage: "background.jpg", }; await act(async () => { const initializedTheme = await result.current.initializeTheme(themeWithImage); expect(initializedTheme).toBeDefined(); }); }); it("should prevent concurrent setTheme calls", async () => { const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const theme1 = { name: "theme1", displayName: "Theme 1", schemaVersion: 1, }; const theme2 = { name: "theme2", displayName: "Theme 2", schemaVersion: 1, }; // Call setTheme twice rapidly await act(async () => { const promise1 = result.current.setTheme(theme1); const promise2 = result.current.setTheme(theme2); await Promise.all([promise1, promise2]); }); // Should have set one of them (the concurrent call should be prevented) expect(result.current.currentTheme).toBeTruthy(); }); it("should handle errors gracefully", async () => { const applyThemeSpy = vi.spyOn(themeModule, "applyTheme"); applyThemeSpy.mockImplementation(() => { throw new Error("Test error"); }); const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); const testTheme = { name: "test", displayName: "Test Theme", schemaVersion: 1, }; await act(async () => { await result.current.setTheme(testTheme); }); expect(result.current.error).toBe("Failed to set theme"); }); it("should clear error when successfully setting theme", async () => { const applyThemeSpy = vi.spyOn(themeModule, "applyTheme"); const { result } = renderHook(() => useSimpleTheme(), { wrapper: SimpleThemeProvider, }); await waitFor(() => { expect(result.current.isLoading).toBe(false); }); // First, cause an error applyThemeSpy.mockImplementationOnce(() => { throw new Error("Test error"); }); const theme1 = { name: "theme1", displayName: "Theme 1", schemaVersion: 1, }; await act(async () => { await result.current.setTheme(theme1); }); expect(result.current.error).toBe("Failed to set theme"); // Now set successfully applyThemeSpy.mockImplementation(vi.fn()); const theme2 = { name: "theme2", displayName: "Theme 2", schemaVersion: 1, }; await act(async () => { await result.current.setTheme(theme2); }); expect(result.current.error).toBe(null); }); }); describe("useSimpleTheme", () => { it("should throw error when used outside SimpleThemeProvider", () => { expect(() => { renderHook(() => useSimpleTheme()); }).toThrow("useSimpleTheme must be used within a SimpleThemeProvider"); }); });