TopicItem.test.tsx (4059B)
1 import { describe, it, expect, vi, beforeEach } from "vitest"; 2 import { render, screen, fireEvent } from "@testing-library/react"; 3 import { TopicItem } from "./TopicItem"; 4 import { useTopicStore } from "../../store/topic"; 5 6 const mockTopic = { 7 id: "t1", 8 name: "Work", 9 color: "#6366f1", 10 sort_order: 0, 11 created_at: Date.now(), 12 session_count: 3, 13 }; 14 15 beforeEach(() => { 16 useTopicStore.setState({ 17 topics: [mockTopic], 18 selectedTopicId: null, 19 }); 20 vi.restoreAllMocks(); 21 }); 22 23 describe("TopicItem", () => { 24 it("renders topic name", () => { 25 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 26 expect(screen.getByText("Work")).toBeInTheDocument(); 27 }); 28 29 it("renders session count", () => { 30 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 31 expect(screen.getByText("3")).toBeInTheDocument(); 32 }); 33 34 it("renders color indicator", () => { 35 const { container } = render( 36 <TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />, 37 ); 38 const colorDot = container.querySelector("[style*='background-color']"); 39 expect(colorDot).toBeInTheDocument(); 40 }); 41 42 it("calls onSelect when clicked", () => { 43 const onSelect = vi.fn(); 44 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={onSelect} />); 45 fireEvent.click(screen.getByText("Work")); 46 expect(onSelect).toHaveBeenCalled(); 47 }); 48 49 it("applies selected styling when isSelected is true", () => { 50 const { container } = render( 51 <TopicItem topic={mockTopic} isSelected={true} onSelect={vi.fn()} />, 52 ); 53 const wrapper = container.firstElementChild; 54 expect(wrapper?.className).toContain("bg-gray-700"); 55 expect(wrapper?.className).toContain("text-white"); 56 }); 57 58 it("applies unselected styling when isSelected is false", () => { 59 const { container } = render( 60 <TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />, 61 ); 62 const wrapper = container.firstElementChild; 63 expect(wrapper?.className).toContain("text-gray-400"); 64 }); 65 66 it("opens context menu on right click", () => { 67 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 68 fireEvent.contextMenu(screen.getByText("Work")); 69 expect(screen.getByText("Rename")).toBeInTheDocument(); 70 expect(screen.getByText("Delete")).toBeInTheDocument(); 71 }); 72 73 it("clicking Rename enables editing mode", () => { 74 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 75 fireEvent.contextMenu(screen.getByText("Work")); 76 fireEvent.click(screen.getByText("Rename")); 77 const input = screen.getByDisplayValue("Work"); 78 expect(input).toBeInTheDocument(); 79 }); 80 81 it("clicking Delete calls deleteTopic", async () => { 82 vi.spyOn(useTopicStore.getState(), "deleteTopic").mockResolvedValue(undefined); 83 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 84 fireEvent.contextMenu(screen.getByText("Work")); 85 fireEvent.click(screen.getByText("Delete")); 86 expect(useTopicStore.getState().deleteTopic).toHaveBeenCalledWith("t1"); 87 }); 88 89 it("clicking a color button calls updateTopic with new color", async () => { 90 vi.spyOn(useTopicStore.getState(), "updateTopic").mockResolvedValue(undefined); 91 render(<TopicItem topic={mockTopic} isSelected={false} onSelect={vi.fn()} />); 92 fireEvent.contextMenu(screen.getByText("Work")); 93 94 const colorButtons = screen.getAllByRole("button").filter( 95 (btn) => btn.className.includes("rounded-full") && btn.style.backgroundColor, 96 ); 97 expect(colorButtons.length).toBeGreaterThan(0); 98 fireEvent.click(colorButtons[0]); 99 expect(useTopicStore.getState().updateTopic).toHaveBeenCalledWith("t1", { 100 color: expect.any(String), 101 }); 102 }); 103 104 it("renders session_count as 0 when null", () => { 105 const topic = { ...mockTopic, session_count: null }; 106 render(<TopicItem topic={topic} isSelected={false} onSelect={vi.fn()} />); 107 expect(screen.getByText("0")).toBeInTheDocument(); 108 }); 109 });