/**
* 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();
});
});
});