// @vitest-environment jsdom
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ThemeToggle } from "./ThemeToggle";
const mockToggleTheme = vi.hoisted(() => vi.fn());
const mockTheme = vi.hoisted(() => ({ value: "dark" as "dark" | "light" }));
vi.mock("../context/ThemeContext", () => ({
useTheme: () => ({
theme: mockTheme.value,
toggleTheme: mockToggleTheme,
}),
}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
async function flushReact() {
await act(async () => {
await Promise.resolve();
});
}
describe("ThemeToggle", () => {
let container: HTMLDivElement;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
mockTheme.value = "dark";
});
afterEach(() => {
container.remove();
document.body.innerHTML = "";
vi.clearAllMocks();
});
it("renders an icon button by default with the 'switch to light' label when current theme is dark", async () => {
const root = createRoot(container);
await act(async () => {
root.render();
});
await flushReact();
const button = container.querySelector("button");
expect(button).not.toBeNull();
expect(button?.getAttribute("aria-label")).toBe("Switch to light mode");
expect(button?.getAttribute("title")).toBe("Switch to light mode");
await act(async () => {
button?.click();
});
expect(mockToggleTheme).toHaveBeenCalledTimes(1);
await act(async () => root.unmount());
});
it("renders a menu-action row when variant='menu-action' and includes the description text", async () => {
const root = createRoot(container);
await act(async () => {
root.render();
});
await flushReact();
expect(container.textContent).toContain("Switch to light mode");
expect(container.textContent).toContain("Toggle the app appearance.");
await act(async () => root.unmount());
});
it("calls onAfterToggle after toggling (used by SidebarAccountMenu to close the popover)", async () => {
const onAfterToggle = vi.fn();
const root = createRoot(container);
await act(async () => {
root.render();
});
await flushReact();
const button = container.querySelector("button");
await act(async () => {
button?.click();
});
expect(mockToggleTheme).toHaveBeenCalledTimes(1);
expect(onAfterToggle).toHaveBeenCalledTimes(1);
await act(async () => root.unmount());
});
it("flips label and icon when current theme is light", async () => {
mockTheme.value = "light";
const root = createRoot(container);
await act(async () => {
root.render();
});
await flushReact();
const button = container.querySelector("button");
expect(button?.getAttribute("aria-label")).toBe("Switch to dark mode");
await act(async () => root.unmount());
});
});