/* Copyright 2026 Marimo. All rights reserved. */ import { act, fireEvent, render, screen } from "@testing-library/react"; import { createStore, Provider } from "jotai"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { ModalProvider } from "@/components/modal/ImperativeModal"; import { TooltipProvider } from "@/components/ui/tooltip"; import { type Chat, type ChatId, chatStateAtom } from "@/core/ai/state"; import { ChatHistoryPopover } from "../chat-history-popover"; const activeId = "active" as ChatId; const olderId = "older" as ChatId; function chat(id: ChatId): Chat { return { id, title: `${id} conversation`, messages: [ { id: `${id}-message`, role: "user", parts: [{ type: "text", text: "Hello" }], }, ], createdAt: Date.now(), updatedAt: Date.now(), }; } function setup(isLoading = false) { const store = createStore(); const setActiveChat = vi.fn(); const onDeleteChats = vi.fn(); render( , ); act(() => store.set(chatStateAtom, { chats: new Map([ [activeId, chat(activeId)], [olderId, chat(olderId)], ]), activeChatId: activeId, }), ); fireEvent.click(screen.getByRole("button", { name: "Previous chats" })); return { store, setActiveChat, onDeleteChats }; } describe("chat history management", () => { beforeEach(() => window.localStorage.clear()); afterEach(() => vi.restoreAllMocks()); it("deletes individual chats immediately without selecting them or closing history", () => { const { setActiveChat, onDeleteChats } = setup(); fireEvent.click( screen.getByRole("button", { name: "Delete chat: older conversation" }), ); expect(screen.queryByRole("alertdialog")).not.toBeInTheDocument(); expect(screen.getByPlaceholderText("Search chat history...")).toBeVisible(); expect(onDeleteChats).toHaveBeenCalledExactlyOnceWith(olderId); expect(setActiveChat).not.toHaveBeenCalled(); }); it("deletes all chats only after confirmation", () => { const { onDeleteChats } = setup(); fireEvent.change(screen.getByPlaceholderText("Search chat history..."), { target: { value: "older" }, }); fireEvent.click(screen.getByRole("button", { name: "Delete all chats" })); expect(screen.getByRole("alertdialog")).toHaveTextContent("all notebooks"); expect(onDeleteChats).not.toHaveBeenCalled(); fireEvent.click(screen.getByRole("button", { name: "Delete all chats" })); expect(onDeleteChats).toHaveBeenCalledExactlyOnceWith(null); }); it("keeps focus in history after deleting the focused chat", () => { const { store, onDeleteChats } = setup(); onDeleteChats.mockImplementation(() => { store.set(chatStateAtom, (prev) => { const chats = new Map(prev.chats); chats.delete(olderId); return { ...prev, chats }; }); }); const searchInput = screen.getByPlaceholderText("Search chat history..."); fireEvent.change(searchInput, { target: { value: "older" } }); const deleteButton = screen.getByRole("button", { name: "Delete chat: older conversation", }); act(() => deleteButton.focus()); expect(deleteButton).toHaveFocus(); fireEvent.click(deleteButton); expect(deleteButton).not.toBeInTheDocument(); expect(searchInput).toHaveFocus(); expect(screen.getByText("No chats found")).toBeVisible(); }); it("prevents deleting a streaming chat while allowing cleanup of other chats", () => { setup(true); expect( screen.getByRole("button", { name: "Delete chat: active conversation" }), ).toBeDisabled(); expect( screen.getByRole("button", { name: "Delete all chats" }), ).toBeDisabled(); expect( screen.getByRole("button", { name: "Delete chat: older conversation" }), ).toBeEnabled(); }); it("warns above 50 chats and clears the warning after cleanup", () => { const { store } = setup(); const chats = new Map( Array.from({ length: 51 }, (_, index) => { const id = `chat-${index}` as ChatId; return [id, chat(id)]; }), ); act(() => store.set(chatStateAtom, { chats, activeChatId: null })); const warning = screen.getByText( /Delete old chats to free up browser storage/, ); expect(warning).toBeVisible(); expect(warning).toHaveTextContent( "You have over 50 chats. Delete old chats to free up browser storage.", ); act(() => store.set(chatStateAtom, (prev) => ({ ...prev, chats: new Map([...prev.chats].slice(1)), })), ); expect( screen.queryByText(/Delete old chats to free up browser storage/), ).not.toBeInTheDocument(); expect( screen.getByText("Chats are shared across notebooks."), ).toBeVisible(); }); });