SessionList.test.tsx (4233B)
1 import { describe, it, expect, vi, beforeEach } from "vitest"; 2 import { render, screen, fireEvent, waitFor } from "@testing-library/react"; 3 import { SessionList } from "./SessionList"; 4 import { useSessionStore } from "../../store/session"; 5 6 const sessions = [ 7 { id: "s1", name: "Chat One", created_at: 1000, message_count: 1 }, 8 { id: "s2", name: "Chat Two", created_at: 2000, message_count: 1 }, 9 ]; 10 11 beforeEach(() => { 12 useSessionStore.setState({ 13 sessions: [], 14 activeSessionId: null, 15 loading: false, 16 error: null, 17 _allSessions: [], 18 }); 19 vi.restoreAllMocks(); 20 }); 21 22 const mockFetchReturn = (data: unknown) => 23 vi.fn().mockResolvedValue({ ok: true, json: async () => data }); 24 25 describe("SessionList", () => { 26 it("shows 'No sessions yet' when empty", () => { 27 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [] })); 28 render(<SessionList />); 29 expect(screen.getByText("No sessions yet")).toBeInTheDocument(); 30 }); 31 32 it("renders session names", async () => { 33 vi.stubGlobal("fetch", mockFetchReturn({ sessions })); 34 render(<SessionList />); 35 await waitFor(() => { 36 expect(screen.getByText("Chat One")).toBeInTheDocument(); 37 expect(screen.getByText("Chat Two")).toBeInTheDocument(); 38 }); 39 }); 40 41 it("highlights active session", () => { 42 useSessionStore.setState({ 43 sessions: [sessions[0]], 44 _allSessions: [sessions[0]], 45 activeSessionId: "s1", 46 }); 47 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [sessions[0]] })); 48 const { container } = render(<SessionList />); 49 const activeItem = container.querySelector(".bg-indigo-600\\/20"); 50 expect(activeItem).toBeTruthy(); 51 }); 52 53 it("shows creation date in metadata", async () => { 54 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [{ id: "s1", name: "Chat", created_at: 1700000000, message_count: 1 }] })); 55 render(<SessionList />); 56 await waitFor(() => { 57 expect(screen.getByText(/2023/)).toBeInTheDocument(); 58 }); 59 }); 60 61 it("opens context menu on ⋮ click", async () => { 62 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [sessions[0]] })); 63 render(<SessionList />); 64 await waitFor(() => expect(screen.getByText("Chat One")).toBeInTheDocument()); 65 fireEvent.click(screen.getByText("⋮")); 66 await waitFor(() => { 67 expect(screen.getByText("Rename")).toBeInTheDocument(); 68 expect(screen.getByText("Delete")).toBeInTheDocument(); 69 }); 70 }); 71 72 it("entering rename mode shows input", async () => { 73 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [{ id: "s1", name: "Old Name", message_count: 1 }] })); 74 render(<SessionList />); 75 await waitFor(() => expect(screen.getByText("Old Name")).toBeInTheDocument()); 76 fireEvent.click(screen.getByText("⋮")); 77 await waitFor(() => expect(screen.getByText("Rename")).toBeInTheDocument()); 78 fireEvent.click(screen.getByText("Rename")); 79 await waitFor(() => { 80 expect(screen.getByDisplayValue("Old Name")).toBeInTheDocument(); 81 }); 82 }); 83 84 it("delete confirmation shows cancel button", async () => { 85 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [{ id: "s1", name: "To Delete", message_count: 1 }] })); 86 render(<SessionList />); 87 await waitFor(() => expect(screen.getByText("To Delete")).toBeInTheDocument()); 88 fireEvent.click(screen.getByText("⋮")); 89 await waitFor(() => expect(screen.getByText("Delete")).toBeInTheDocument()); 90 const deleteBtns = screen.getAllByText("Delete"); 91 fireEvent.click(deleteBtns[0]); 92 await waitFor(() => { 93 expect(screen.getByText("Cancel")).toBeInTheDocument(); 94 }); 95 }); 96 97 it("new session button clears active session instead of creating one", async () => { 98 useSessionStore.setState({ 99 sessions: [sessions[0]], 100 _allSessions: [sessions[0]], 101 activeSessionId: "s1", 102 }); 103 vi.stubGlobal("fetch", mockFetchReturn({ sessions: [sessions[0]] })); 104 render(<SessionList />); 105 fireEvent.click(screen.getByTitle("New session")); 106 expect(useSessionStore.getState().activeSessionId).toBeNull(); 107 // Should NOT call POST /api/sessions 108 expect(fetch).not.toHaveBeenCalledWith( 109 "/api/sessions", 110 expect.objectContaining({ method: "POST" }), 111 ); 112 }); 113 });