import { fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { ChatHeader } from "./ChatHeader"; interface ChatHeaderProps { projectPath: string; onCloseProject: () => void; contextUsage: { used: number; total: number; percentage: number }; onClearSession: () => void; model: string; availableModels: string[]; claudeModels: string[]; hasAnthropicKey: boolean; onModelChange: (model: string) => void; enableTools: boolean; onToggleTools: (enabled: boolean) => void; } function makeProps(overrides: Partial = {}): ChatHeaderProps { return { projectPath: "/test/project", onCloseProject: vi.fn(), contextUsage: { used: 1000, total: 10000, percentage: 10 }, onClearSession: vi.fn(), model: "claude-sonnet", availableModels: ["llama3"], claudeModels: ["claude-sonnet"], hasAnthropicKey: true, onModelChange: vi.fn(), enableTools: true, onToggleTools: vi.fn(), ...overrides, }; } describe("ChatHeader", () => { it("renders project path", () => { render(); expect(screen.getByText("/test/project")).toBeInTheDocument(); }); it("calls onCloseProject when close button is clicked", () => { const onCloseProject = vi.fn(); render(); fireEvent.click(screen.getByText("\u2715")); expect(onCloseProject).toHaveBeenCalled(); }); it("displays context percentage with green emoji when low", () => { render( , ); expect(screen.getByText(/10%/)).toBeInTheDocument(); }); it("displays yellow emoji when context is 75-89%", () => { render( , ); expect(screen.getByText(/80%/)).toBeInTheDocument(); }); it("displays red emoji when context is 90%+", () => { render( , ); expect(screen.getByText(/95%/)).toBeInTheDocument(); }); it("calls onClearSession when New Session button is clicked", () => { const onClearSession = vi.fn(); render(); fireEvent.click(screen.getByText(/New Session/)); expect(onClearSession).toHaveBeenCalled(); }); it("renders select dropdown when model options are available", () => { render(); const select = screen.getByRole("combobox"); expect(select).toBeInTheDocument(); }); it("renders text input when no model options are available", () => { render( , ); expect(screen.getByPlaceholderText("Model")).toBeInTheDocument(); }); it("calls onModelChange when model is selected from dropdown", () => { const onModelChange = vi.fn(); render(); const select = screen.getByRole("combobox"); fireEvent.change(select, { target: { value: "llama3" } }); expect(onModelChange).toHaveBeenCalledWith("llama3"); }); it("calls onModelChange when text is typed in model input", () => { const onModelChange = vi.fn(); render( , ); const input = screen.getByPlaceholderText("Model"); fireEvent.change(input, { target: { value: "custom-model" } }); expect(onModelChange).toHaveBeenCalledWith("custom-model"); }); it("calls onToggleTools when checkbox is toggled", () => { const onToggleTools = vi.fn(); render(); const checkbox = screen.getByRole("checkbox"); fireEvent.click(checkbox); expect(onToggleTools).toHaveBeenCalled(); }); it("displays the build timestamp in human-readable format", () => { render(); expect(screen.getByText("Built: 2026-01-01 00:00")).toBeInTheDocument(); }); it("displays Story Kit branding in the header", () => { render(); expect(screen.getByText("Story Kit")).toBeInTheDocument(); }); it("shows disabled placeholder when claudeModels is empty and no API key", () => { render( , ); expect( screen.getByText("Add Anthropic API key to load models"), ).toBeInTheDocument(); }); // ── Close button hover/focus handlers ───────────────────────────────────── it("close button changes background on mouseOver and resets on mouseOut", () => { render(); const closeBtn = screen.getByText("\u2715"); fireEvent.mouseOver(closeBtn); expect(closeBtn.style.background).toBe("rgb(51, 51, 51)"); fireEvent.mouseOut(closeBtn); expect(closeBtn.style.background).toBe("transparent"); }); it("close button changes background on focus and resets on blur", () => { render(); const closeBtn = screen.getByText("\u2715"); fireEvent.focus(closeBtn); expect(closeBtn.style.background).toBe("rgb(51, 51, 51)"); fireEvent.blur(closeBtn); expect(closeBtn.style.background).toBe("transparent"); }); // ── New Session button hover/focus handlers ─────────────────────────────── it("New Session button changes style on mouseOver and resets on mouseOut", () => { render(); const sessionBtn = screen.getByText(/New Session/); fireEvent.mouseOver(sessionBtn); expect(sessionBtn.style.backgroundColor).toBe("rgb(63, 63, 63)"); expect(sessionBtn.style.color).toBe("rgb(204, 204, 204)"); fireEvent.mouseOut(sessionBtn); expect(sessionBtn.style.backgroundColor).toBe("rgb(47, 47, 47)"); expect(sessionBtn.style.color).toBe("rgb(136, 136, 136)"); }); it("New Session button changes style on focus and resets on blur", () => { render(); const sessionBtn = screen.getByText(/New Session/); fireEvent.focus(sessionBtn); expect(sessionBtn.style.backgroundColor).toBe("rgb(63, 63, 63)"); expect(sessionBtn.style.color).toBe("rgb(204, 204, 204)"); fireEvent.blur(sessionBtn); expect(sessionBtn.style.backgroundColor).toBe("rgb(47, 47, 47)"); expect(sessionBtn.style.color).toBe("rgb(136, 136, 136)"); }); });