| ... | @@ -0,0 +1,100 @@ |
| 1 | import { HTML_RENDER_DEFAULT_FONTS, type HtmlRenderTheme } from "@t3tools/shared/htmlRender"; |
| 2 | import { BUILT_IN_THEME_IDS } from "@t3tools/shared/themePalettes"; |
| 3 | import { act, createElement, useEffect } from "react"; |
| 4 | import { create, type ReactTestRenderer } from "react-test-renderer"; |
| 5 | import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 6 | |
| 7 | import { |
| 8 | getStandardThemeColors, |
| 9 | setEnvironmentThemes, |
| 10 | type ThemeAppearance, |
| 11 | type ThemeHalves, |
| 12 | type ThemePreference, |
| 13 | } from "../themePalette"; |
| 14 | import { useHtmlRenderTheme } from "./useHtmlRenderTheme"; |
| 15 | |
| 16 | const selection = vi.hoisted(() => ({ |
| 17 | theme: "ocean" as ThemePreference, |
| 18 | resolvedTheme: "dark" as ThemeAppearance, |
| 19 | themeHalves: null as ThemeHalves | null, |
| 20 | })); |
| 21 | |
| 22 | vi.mock("./useTheme", () => ({ useTheme: () => selection })); |
| 23 | vi.mock("./useSettings", () => ({ |
| 24 | useClientSettings: ( |
| 25 | select: (settings: { fontFamilySans: string; fontFamilyCode: string }) => string, |
| 26 | ) => select({ fontFamilySans: "", fontFamilyCode: "" }), |
| 27 | })); |
| 28 | |
| 29 | let renderer: ReactTestRenderer | undefined; |
| 30 | |
| 31 | beforeEach(() => { |
| 32 | vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); |
| 33 | selection.themeHalves = null; |
| 34 | }); |
| 35 | |
| 36 | afterEach(() => { |
| 37 | act(() => renderer?.unmount()); |
| 38 | renderer = undefined; |
| 39 | setEnvironmentThemes([]); |
| 40 | vi.unstubAllGlobals(); |
| 41 | }); |
| 42 | |
| 43 | /** Returns the committed hook result after React has flushed mount effects. */ |
| 44 | function readRenderTheme(): HtmlRenderTheme { |
| 45 | let theme: HtmlRenderTheme | undefined; |
| 46 | /** Captures the theme in an effect to avoid mutating test state during render. */ |
| 47 | function Probe() { |
| 48 | const resolved = useHtmlRenderTheme(); |
| 49 | useEffect(() => { |
| 50 | theme = resolved; |
| 51 | }, [resolved]); |
| 52 | return null; |
| 53 | } |
| 54 | act(() => { |
| 55 | renderer = create(createElement(Probe)); |
| 56 | }); |
| 57 | if (!theme) throw new Error("The HTML render theme was not resolved."); |
| 58 | return theme; |
| 59 | } |
| 60 | |
| 61 | describe("useHtmlRenderTheme", () => { |
| 62 | for (const appearance of ["light", "dark"] as const) { |
| 63 | it.each([appearance, ...BUILT_IN_THEME_IDS])( |
| 64 | `injects hex colors for %s in ${appearance} mode`, |
| 65 | (preference) => { |
| 66 | selection.theme = preference; |
| 67 | selection.resolvedTheme = appearance; |
| 68 | |
| 69 | const theme = readRenderTheme(); |
| 70 | |
| 71 | expect(theme.appearance).toBe(appearance); |
| 72 | for (const [variable, value] of Object.entries(theme.variables)) { |
| 73 | if (variable === "--radius" || variable.startsWith("--font-")) continue; |
| 74 | expect(value, variable).toMatch(/^#[\da-f]{6}(?:[\da-f]{2})?$/i); |
| 75 | } |
| 76 | expect(theme.variables["--font-sans"]).toBe(HTML_RENDER_DEFAULT_FONTS.sans); |
| 77 | expect(theme.variables["--font-mono"]).toBe(HTML_RENDER_DEFAULT_FONTS.mono); |
| 78 | expect(theme.variables["--radius"]).toBe("0.625rem"); |
| 79 | }, |
| 80 | ); |
| 81 | } |
| 82 | |
| 83 | it("preserves alpha when converting a published palette", () => { |
| 84 | setEnvironmentThemes([ |
| 85 | { |
| 86 | id: "transparent", |
| 87 | label: "Transparent", |
| 88 | appearance: "dark", |
| 89 | colors: { ...getStandardThemeColors("dark"), accent: "oklch(0.5 0 0 / 0.5)" }, |
| 90 | }, |
| 91 | ]); |
| 92 | selection.theme = "transparent"; |
| 93 | selection.resolvedTheme = "dark"; |
| 94 | |
| 95 | const theme = readRenderTheme(); |
| 96 | |
| 97 | expect(theme.variables["--accent"]).toBe("#63636380"); |
| 98 | expect(theme.variables["--chart-1"]).toBe("#63636380"); |
| 99 | }); |
| 100 | }); |