/** * @file AppearanceSettings.test.tsx * @description Tests for the Settings → Appearance section: the language switch, the text size, the terminal font and size pickers, and the mode switch and the per-mode theme cards writing through useTheme to ``. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, beforeEach, vi } from "vitest"; import { act, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import i18n from "../../../i18n"; import { AppearanceSettings } from "../AppearanceSettings"; vi.mock("../PetSettings", () => ({ PetSettings: () =>
})); const root = document.documentElement; describe("AppearanceSettings", () => { beforeEach(() => { localStorage.clear(); root.classList.remove("dark"); root.removeAttribute("data-theme"); root.removeAttribute("data-mode"); root.style.removeProperty("font-size"); }); it("switches the interface language and holds the pet block", async () => { const user = userEvent.setup(); render(); expect(screen.getByTestId("pet-settings").closest("section")).toHaveAttribute("id", "pet"); const group = screen.getByRole("radiogroup", { name: "Interface language" }); expect(within(group).getByRole("radio", { name: "English" })).toHaveAttribute( "aria-checked", "true" ); await user.click(within(group).getByRole("radio", { name: "Tiếng Việt" })); expect(i18n.language).toBe("vi"); await act(async () => { await i18n.changeLanguage("en"); }); }); it("renders every theme of both modes, defaults selected", () => { render(); const pressed = screen .getAllByRole("button", { pressed: true }) .map((b) => b.getAttribute("aria-label") ?? b.textContent); expect(pressed).toHaveLength(2); // both Default cards expect(screen.getByRole("radio", { name: /Dark/ })).toHaveAttribute("aria-checked", "true"); for (const name of [ "Darcula", "Apple Dark", "Orca Dark", "Apple Light", "Canvas", "Orca Light", ]) { expect(screen.getByText(name)).toBeInTheDocument(); } }); it("picking a dark theme applies it at once; a light pick waits for light mode", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("button", { name: /Darcula/ })); expect(root.dataset.theme).toBe("darcula"); await user.click(screen.getByRole("button", { name: /Canvas/ })); expect(root.dataset.theme).toBe("darcula"); await user.click(screen.getByRole("radio", { name: /^Light$/ })); expect(root.classList.contains("dark")).toBe(false); expect(root.dataset.theme).toBe("canvas"); expect(screen.getByRole("button", { name: /Canvas/ })).toHaveAttribute("aria-pressed", "true"); }); it("the System mode is checked after choosing it", async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole("radio", { name: "System" })); expect(screen.getByRole("radio", { name: "System" })).toHaveAttribute("aria-checked", "true"); expect(localStorage.getItem("theme")).toBe("system"); }); it("each swatch scopes its own mode and theme", () => { const { container } = render(); expect(container.querySelector('.dark[data-theme="darcula"]')).not.toBeNull(); expect(container.querySelector('[data-mode="light"][data-theme="canvas"]')).not.toBeNull(); expect(container.querySelector('[data-mode="light"]:not([data-theme])')).not.toBeNull(); }); it("picks the terminal font, JetBrains Mono by default, and warns on System", async () => { const user = userEvent.setup(); render(); const trigger = screen.getByRole("button", { name: /Terminal font/ }); expect(trigger).toHaveTextContent("JetBrains Mono"); const pick = async (name: string) => { await user.click(screen.getByRole("button", { name: /Terminal font/ })); await user.click(screen.getByRole("option", { name })); }; await pick("Maple Mono (rounded)"); expect(localStorage.getItem("agenthub.terminal.font")).toBe("maple"); expect(screen.getByTestId("terminal-font-preview").style.fontFamily).toContain("Maple Mono"); expect(screen.queryByText(/may lack some Vietnamese letters/)).toBeNull(); await pick("System font"); expect(localStorage.getItem("agenthub.terminal.font")).toBe("system"); expect(screen.getByText(/may lack some Vietnamese letters/)).toBeInTheDocument(); }); it("sets the interface text size on , the default leaving it unset", async () => { const user = userEvent.setup(); render(); const group = screen.getByRole("radiogroup", { name: "Interface text" }); expect(within(group).getByRole("radio", { name: "Default" })).toHaveAttribute( "aria-checked", "true" ); await user.click(within(group).getByRole("radio", { name: "Larger" })); expect(root.style.fontSize).toBe("18px"); expect(localStorage.getItem("agenthub.ui.fontSize")).toBe("18"); await user.click(within(group).getByRole("radio", { name: "Default" })); expect(root.style.fontSize).toBe(""); }); it("picks the terminal font size and tells open terminals", async () => { const user = userEvent.setup(); const heard = vi.fn(); window.addEventListener("agenthub-font-size", heard); render(); expect(screen.getByRole("button", { name: /^Size/ })).toHaveTextContent("13px"); await user.click(screen.getByRole("button", { name: /^Size/ })); await user.click(screen.getByRole("option", { name: "16px" })); expect(localStorage.getItem("agenthub.terminal.fontSize")).toBe("16"); expect(screen.getByTestId("terminal-font-preview").style.fontSize).toBe("16px"); expect(heard).toHaveBeenCalled(); window.removeEventListener("agenthub-font-size", heard); }); });