/** * Tests for Settings component. * * TDD: These tests define the expected behavior before implementation. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Settings } from "@/components/Settings"; import type { SettingsData } from "@/components/Settings"; const defaultSettings: SettingsData = { schemaVersion: 1, hotkeys: { toggle: "Meta+Shift+M", capture: "Meta+Shift+C", }, lorePath: null, reconciliationHours: 6, floatingWidget: false, defaultDefer: "1h", sounds: true, theme: "dark", notifications: true, }; describe("Settings", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("loading state", () => { it("displays current settings on mount", () => { render(); // Check hotkey displays expect(screen.getByDisplayValue("Meta+Shift+M")).toBeInTheDocument(); expect(screen.getByDisplayValue("Meta+Shift+C")).toBeInTheDocument(); }); it("shows data directory info", () => { render( ); expect(screen.getByText(/\.local\/share\/mc/)).toBeInTheDocument(); }); }); describe("theme toggle", () => { it("renders theme toggle with current value", () => { render(); const themeToggle = screen.getByRole("switch", { name: /dark mode/i }); expect(themeToggle).toBeChecked(); }); it("calls onSave when theme is toggled", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const themeToggle = screen.getByRole("switch", { name: /dark mode/i }); await user.click(themeToggle); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ theme: "light", }) ); }); }); describe("notification preferences", () => { it("renders notification toggle", () => { render(); const notifToggle = screen.getByRole("switch", { name: /notifications/i }); expect(notifToggle).toBeChecked(); }); it("calls onSave when notifications toggled", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const notifToggle = screen.getByRole("switch", { name: /notifications/i }); await user.click(notifToggle); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ notifications: false, }) ); }); }); describe("sound effects", () => { it("renders sound effects toggle", () => { render(); const soundToggle = screen.getByRole("switch", { name: /sound effects/i }); expect(soundToggle).toBeChecked(); }); it("calls onSave when sound effects toggled", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const soundToggle = screen.getByRole("switch", { name: /sound effects/i }); await user.click(soundToggle); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ sounds: false, }) ); }); }); describe("floating widget", () => { it("renders floating widget toggle", () => { render(); const widgetToggle = screen.getByRole("switch", { name: /floating widget/i, }); expect(widgetToggle).not.toBeChecked(); }); it("calls onSave when floating widget toggled", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const widgetToggle = screen.getByRole("switch", { name: /floating widget/i, }); await user.click(widgetToggle); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ floatingWidget: true, }) ); }); }); describe("hotkey settings", () => { it("validates hotkey format", async () => { const user = userEvent.setup(); render(); const toggleInput = screen.getByLabelText(/toggle hotkey/i); await user.clear(toggleInput); await user.type(toggleInput, "invalid"); expect(screen.getByText(/Invalid hotkey format/i)).toBeInTheDocument(); }); it("accepts valid hotkey format", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const toggleInput = screen.getByLabelText(/toggle hotkey/i); await user.clear(toggleInput); await user.type(toggleInput, "Meta+Shift+K"); await user.tab(); // Blur to trigger save expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ hotkeys: expect.objectContaining({ toggle: "Meta+Shift+K", }), }) ); }); }); describe("reconciliation interval", () => { it("displays current interval", () => { render(); expect(screen.getByDisplayValue("6")).toBeInTheDocument(); }); it("updates interval on change", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const intervalInput = screen.getByLabelText(/reconciliation/i); await user.clear(intervalInput); await user.type(intervalInput, "12"); await user.tab(); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ reconciliationHours: 12, }) ); }); }); describe("default defer duration", () => { it("displays current default defer", () => { render(); const deferSelect = screen.getByLabelText(/default defer/i); expect(deferSelect).toHaveValue("1h"); }); it("updates default defer on change", async () => { const user = userEvent.setup(); const onSave = vi.fn(); render(); const deferSelect = screen.getByLabelText(/default defer/i); await user.selectOptions(deferSelect, "3h"); expect(onSave).toHaveBeenCalledWith( expect.objectContaining({ defaultDefer: "3h", }) ); }); }); describe("keyboard shortcuts display", () => { it("shows keyboard shortcuts section", () => { render(); expect(screen.getByText(/keyboard shortcuts/i)).toBeInTheDocument(); }); it("displays common shortcuts", () => { render(); // Check for common shortcut displays expect(screen.getByText(/start task/i)).toBeInTheDocument(); expect(screen.getByText(/skip task/i)).toBeInTheDocument(); expect(screen.getByText(/command palette/i)).toBeInTheDocument(); }); }); describe("lore path", () => { it("shows default lore path when null", () => { render(); expect( screen.getByPlaceholderText(/\.local\/share\/lore/i) ).toBeInTheDocument(); }); it("shows custom lore path when set", () => { const settingsWithPath = { ...defaultSettings, lorePath: "/custom/path/lore.db", }; render(); expect(screen.getByDisplayValue("/custom/path/lore.db")).toBeInTheDocument(); }); }); describe("section organization", () => { it("groups settings into sections", () => { render(); // Check for section headers (h2 elements) expect(screen.getByRole("heading", { name: /appearance/i })).toBeInTheDocument(); expect(screen.getByRole("heading", { name: /behavior/i })).toBeInTheDocument(); expect(screen.getByRole("heading", { name: /hotkeys/i })).toBeInTheDocument(); expect(screen.getByRole("heading", { name: /^data$/i })).toBeInTheDocument(); }); }); });