import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MemoryRouter } from "react-router";
import { ConversationTabs } from "#/components/features/conversation/conversation-tabs/conversation-tabs";
import { useConversationStore } from "#/stores/conversation-store";
const TASK_CONVERSATION_ID = "task-ec03fb2ab8604517b24af632b058c2fd";
const REAL_CONVERSATION_ID = "conv-abc123";
let mockConversationId = TASK_CONVERSATION_ID;
vi.mock("#/hooks/use-conversation-id", () => ({
useConversationId: () => ({ conversationId: mockConversationId }),
}));
let mockHasTaskList = false;
vi.mock("#/hooks/use-task-list", () => ({
useTaskList: () => ({
hasTaskList: mockHasTaskList,
taskList: [],
}),
}));
const createWrapper = (conversationId: string) => {
return ({ children }: { children: React.ReactNode }) => (
{children}
);
};
describe("ConversationTabs localStorage behavior", () => {
beforeEach(() => {
localStorage.clear();
vi.resetAllMocks();
mockConversationId = TASK_CONVERSATION_ID;
mockHasTaskList = false;
useConversationStore.setState({
selectedTab: null,
isRightPanelShown: false,
hasRightPanelToggled: false,
});
});
describe("task-prefixed conversation IDs", () => {
it("should not create localStorage entries for task-prefixed conversation IDs", () => {
render(, {
wrapper: createWrapper(TASK_CONVERSATION_ID),
});
expect(
localStorage.getItem(`conversation-state-${TASK_CONVERSATION_ID}`),
).toBeNull();
});
});
describe("consolidated localStorage key", () => {
it("should use a single consolidated key for tab state", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
const changesTab = screen.getByTestId("conversation-tab-editor");
await user.click(changesTab);
const consolidatedKey = `conversation-state-${REAL_CONVERSATION_ID}`;
const storedState = localStorage.getItem(consolidatedKey);
expect(storedState).not.toBeNull();
const parsed = JSON.parse(storedState!);
expect(parsed).toHaveProperty("selectedTab");
expect(parsed).toHaveProperty("rightPanelShown");
expect(parsed).toHaveProperty("unpinnedTabs");
});
});
describe("hook integration", () => {
it("should open panel and select tab when clicking a tab while panel is closed", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
// Arrange: Panel is closed, no tab selected
useConversationStore.setState({
selectedTab: null,
isRightPanelShown: false,
hasRightPanelToggled: false,
});
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
// Act: Click the terminal tab
const terminalTab = screen.getByTestId("conversation-tab-terminal");
await user.click(terminalTab);
// Assert: Panel should be open and terminal tab selected
expect(useConversationStore.getState().selectedTab).toBe("terminal");
expect(useConversationStore.getState().hasRightPanelToggled).toBe(true);
// Verify localStorage was updated
const storedState = JSON.parse(
localStorage.getItem(`conversation-state-${REAL_CONVERSATION_ID}`)!,
);
expect(storedState.selectedTab).toBe("terminal");
expect(storedState.rightPanelShown).toBe(true);
});
it("should close panel when clicking the same active tab", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
// Arrange: Panel is open with editor tab selected
useConversationStore.setState({
selectedTab: "editor",
isRightPanelShown: true,
hasRightPanelToggled: true,
});
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
// Act: Click the editor tab again
const editorTab = screen.getByTestId("conversation-tab-editor");
await user.click(editorTab);
// Assert: Panel should be closed
expect(useConversationStore.getState().hasRightPanelToggled).toBe(false);
// Verify localStorage was updated
const storedState = JSON.parse(
localStorage.getItem(`conversation-state-${REAL_CONVERSATION_ID}`)!,
);
expect(storedState.rightPanelShown).toBe(false);
});
it("should switch to different tab when clicking another tab while panel is open", async () => {
mockConversationId = REAL_CONVERSATION_ID;
const user = userEvent.setup();
// Arrange: Panel is open with editor tab selected
useConversationStore.setState({
selectedTab: "editor",
isRightPanelShown: true,
hasRightPanelToggled: true,
});
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
// Act: Click the browser tab
const browserTab = screen.getByTestId("conversation-tab-browser");
await user.click(browserTab);
// Assert: Browser tab should be selected, panel still open
expect(useConversationStore.getState().selectedTab).toBe("browser");
expect(useConversationStore.getState().hasRightPanelToggled).toBe(true);
// Verify localStorage was updated
const storedState = JSON.parse(
localStorage.getItem(`conversation-state-${REAL_CONVERSATION_ID}`)!,
);
expect(storedState.selectedTab).toBe("browser");
});
});
describe("tasklist tab", () => {
beforeEach(() => {
mockConversationId = REAL_CONVERSATION_ID;
mockHasTaskList = true;
});
it("should show tasklist tab when hasTaskList is true", () => {
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
expect(
screen.getByTestId("conversation-tab-tasklist"),
).toBeInTheDocument();
});
it("should select tasklist tab when clicked", async () => {
const user = userEvent.setup();
render(, {
wrapper: createWrapper(REAL_CONVERSATION_ID),
});
const tasklistTab = screen.getByTestId("conversation-tab-tasklist");
await user.click(tasklistTab);
const { selectedTab, hasRightPanelToggled } =
useConversationStore.getState();
expect(selectedTab).toBe("tasklist");
expect(hasRightPanelToggled).toBe(true);
});
});
});