story-kit: merge 109_story_add_test_coverage_for_lozengeflycontext_selectionscreen_and_chatheader_components
This commit is contained in:
192
frontend/src/components/ChatHeader.test.tsx
Normal file
192
frontend/src/components/ChatHeader.test.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
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> = {}): 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(<ChatHeader {...makeProps()} />);
|
||||
expect(screen.getByText("/test/project")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onCloseProject when close button is clicked", () => {
|
||||
const onCloseProject = vi.fn();
|
||||
render(<ChatHeader {...makeProps({ onCloseProject })} />);
|
||||
fireEvent.click(screen.getByText("\u2715"));
|
||||
expect(onCloseProject).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("displays context percentage with green emoji when low", () => {
|
||||
render(
|
||||
<ChatHeader
|
||||
{...makeProps({
|
||||
contextUsage: { used: 1000, total: 10000, percentage: 10 },
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText(/10%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays yellow emoji when context is 75-89%", () => {
|
||||
render(
|
||||
<ChatHeader
|
||||
{...makeProps({
|
||||
contextUsage: { used: 8000, total: 10000, percentage: 80 },
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText(/80%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays red emoji when context is 90%+", () => {
|
||||
render(
|
||||
<ChatHeader
|
||||
{...makeProps({
|
||||
contextUsage: { used: 9500, total: 10000, percentage: 95 },
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText(/95%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onClearSession when New Session button is clicked", () => {
|
||||
const onClearSession = vi.fn();
|
||||
render(<ChatHeader {...makeProps({ onClearSession })} />);
|
||||
fireEvent.click(screen.getByText(/New Session/));
|
||||
expect(onClearSession).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders select dropdown when model options are available", () => {
|
||||
render(<ChatHeader {...makeProps()} />);
|
||||
const select = screen.getByRole("combobox");
|
||||
expect(select).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders text input when no model options are available", () => {
|
||||
render(
|
||||
<ChatHeader {...makeProps({ availableModels: [], claudeModels: [] })} />,
|
||||
);
|
||||
expect(screen.getByPlaceholderText("Model")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onModelChange when model is selected from dropdown", () => {
|
||||
const onModelChange = vi.fn();
|
||||
render(<ChatHeader {...makeProps({ onModelChange })} />);
|
||||
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(
|
||||
<ChatHeader
|
||||
{...makeProps({
|
||||
availableModels: [],
|
||||
claudeModels: [],
|
||||
onModelChange,
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
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(<ChatHeader {...makeProps({ onToggleTools })} />);
|
||||
const checkbox = screen.getByRole("checkbox");
|
||||
fireEvent.click(checkbox);
|
||||
expect(onToggleTools).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows disabled placeholder when claudeModels is empty and no API key", () => {
|
||||
render(
|
||||
<ChatHeader
|
||||
{...makeProps({
|
||||
claudeModels: [],
|
||||
hasAnthropicKey: false,
|
||||
availableModels: ["llama3"],
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
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(<ChatHeader {...makeProps()} />);
|
||||
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(<ChatHeader {...makeProps()} />);
|
||||
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(<ChatHeader {...makeProps()} />);
|
||||
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(<ChatHeader {...makeProps()} />);
|
||||
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)");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user