import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, fireEvent, render, renderHook, screen, waitFor, } from "@testing-library/react"; const archiveAndSwitchMock = vi.fn(); const toastMock = vi.fn(); const handleSendMessageMock = vi.fn(); const taskPRsByTaskId = vi.hoisted(() => ({ value: { "task-1": [{ pr_number: 42, state: "merged" }], } as Record>, })); vi.mock("@/components/state-provider", () => ({ useAppStore: (selector: (state: typeof mockState) => unknown) => selector(mockState), useAppStoreApi: () => ({ getState: () => mockState }), })); vi.mock("@/hooks/use-task-actions", () => ({ useArchiveAndSwitchTask: () => archiveAndSwitchMock, })); vi.mock("@/components/toast-provider", () => ({ useToast: () => ({ toast: toastMock }), })); vi.mock("@/components/github/pr-status-chip", () => ({ PRStatusChip: () => null, })); vi.mock("@/components/task/share/share-button", () => ({ ShareButton: () => null, shareableSessionStateClient: () => false, })); vi.mock("@/components/task/chat/chat-input-container", () => ({ ChatInputContainer: () => null, })); vi.mock("@/components/task/chat/todo-indicator", () => ({ TodoIndicator: () => null, })); vi.mock("@/hooks/use-keyboard-shortcut", () => ({ useKeyboardShortcut: () => undefined, })); vi.mock("@/hooks/use-message-handler", () => ({ buildTaskMentionsContext: vi.fn(), useMessageHandler: () => ({ handleSendMessage: handleSendMessageMock }), })); vi.mock("@/hooks/domains/kanban/use-plan-actions", () => ({ usePlanActions: () => ({ implementPlanHandler: vi.fn(), proceedStepName: null, proceed: vi.fn(), isMoving: false, }), })); vi.mock("@/hooks/domains/session/use-executor-environment-availability", () => ({ useExecutorEnvironmentAvailability: () => ({ unavailable: false, status: null, }), })); vi.mock("@/lib/ws/connection", () => ({ getWebSocketClient: () => ({ send: vi.fn() }), })); vi.mock("@/lib/local-storage", async (importOriginal) => ({ ...(await importOriginal()), markPRClosedBannerDismissed: vi.fn(), markPRMergedBannerDismissed: vi.fn(), wasPRClosedBannerDismissed: () => false, wasPRMergedBannerDismissed: () => false, })); const mockState = { taskPRs: { get byTaskId() { return taskPRsByTaskId.value; }, }, }; import { PRMergedBanner, useSubmitHandler } from "./chat-input-area"; beforeEach(() => { archiveAndSwitchMock.mockResolvedValue(undefined); handleSendMessageMock.mockResolvedValue(undefined); taskPRsByTaskId.value = { "task-1": [{ pr_number: 42, state: "merged" }], }; }); afterEach(() => { cleanup(); vi.restoreAllMocks(); vi.clearAllMocks(); }); describe("PRMergedBanner", () => { it("archives without showing a success toast", async () => { render(); fireEvent.click(screen.getByTestId("pr-merged-archive-button")); await waitFor(() => expect(archiveAndSwitchMock).toHaveBeenCalledWith("task-1")); expect(toastMock).not.toHaveBeenCalled(); }); it("keeps the failure toast when archive fails", async () => { archiveAndSwitchMock.mockRejectedValueOnce(new Error("archive failed")); render(); fireEvent.click(screen.getByTestId("pr-merged-archive-button")); await waitFor(() => expect(toastMock).toHaveBeenCalledWith({ description: "Failed to archive task", variant: "error", }), ); }); }); describe("useSubmitHandler", () => { function panelState(overrides = {}) { return { resolvedSessionId: "session-1", taskId: "task-1", sessionModel: null, activeModel: null, isAgentBusy: false, activeDocument: null, planComments: [], pendingPRFeedback: [], contextFiles: [], prompts: [], markCommentsSent: vi.fn(), clearSessionPlanComments: vi.fn(), handleClearPRFeedback: vi.fn(), clearEphemeral: vi.fn(), addContextFile: vi.fn(), planModeEnabled: false, ...overrides, } as never; } it("shows a toast when sending fails", async () => { vi.spyOn(console, "error").mockImplementation(() => undefined); handleSendMessageMock.mockRejectedValueOnce(new Error("WebSocket request timed out")); const { result } = renderHook(() => useSubmitHandler(panelState())); await act(async () => { await result.current.handleSubmit("hello"); }); expect(toastMock).toHaveBeenCalledWith({ title: "Message send status unknown", description: "The connection dropped or timed out. Refresh the task to confirm whether it went through.", variant: "error", }); }); });