import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { page } from "vitest/browser";
import { ThemeProvider } from "./theme-provider";
import { ThemeMenuItems, ThemeToggle } from "./theme-toggle";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import "@/app/globals.css";
const storageKey = "theme-menu-test";
beforeEach(() => localStorage.removeItem(storageKey));
afterEach(() => {
localStorage.removeItem(storageKey);
document.documentElement.classList.remove("dark", "light");
document.documentElement.style.removeProperty("color-scheme");
vi.restoreAllMocks();
});
it.each([
["system", "System", "Dark"],
["dark", "Dark", "Light"],
["light", "Light", "System"],
])(
"checks the saved %s theme and closes after a new selection",
async (theme, label, nextLabel) => {
const consoleError = vi.spyOn(console, "error");
localStorage.setItem(storageKey, theme);
render(
,
);
await page.getByRole("button", { name: "Toggle theme" }).click();
const selected = await screen.findByRole("menuitemradio", { name: label });
await waitFor(() =>
expect(selected).toHaveAttribute("aria-checked", "true"),
);
expect(
screen.getAllByRole("menuitemradio", { checked: true }),
).toHaveLength(1);
const height = matchMedia("(hover: none) and (pointer: coarse)").matches
? 44
: 32;
await waitFor(() =>
expect(selected.getBoundingClientRect().height).toBe(height),
);
expect(getComputedStyle(selected).paddingLeft).toBe("32px");
const dot = selected.querySelector(
'[data-slot="dropdown-menu-radio-item-indicator"] svg',
);
if (!dot) throw new Error("Missing selected theme dot");
expect(dot.getBoundingClientRect().width).toBe(8);
expect(dot.getBoundingClientRect().right).toBeLessThan(
selected.getBoundingClientRect().left + 32,
);
expect(getComputedStyle(dot).fill).toBe(getComputedStyle(dot).color);
await page.getByRole("menuitemradio", { name: nextLabel }).click();
await waitFor(() => expect(screen.queryByRole("menu")).toBeNull());
expect(localStorage.getItem(storageKey)).toBe(nextLabel.toLowerCase());
await page.getByRole("button", { name: "Toggle theme" }).click();
await waitFor(() =>
expect(
screen.getByRole("menuitemradio", { name: nextLabel }),
).toHaveAttribute("aria-checked", "true"),
);
expect(
screen.getAllByRole("menuitemradio", { checked: true }),
).toHaveLength(1);
expect(consoleError.mock.calls.flat().join(" ")).not.toMatch(
/uncontrolled.*controlled|controlled.*uncontrolled/,
);
},
);
it("keeps the theme group controlled when its menu mounts before the preference", async () => {
const consoleError = vi.spyOn(console, "error");
render(
Account
,
);
const system = await screen.findByRole("menuitemradio", { name: "System" });
await waitFor(() => expect(system).toHaveAttribute("aria-checked", "true"));
expect(consoleError.mock.calls.flat().join(" ")).not.toMatch(
/uncontrolled.*controlled|controlled.*uncontrolled/,
);
});