import React, { createRef } from "react"; import { renderHook } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { ToastProvider } from "@/components/toast-provider"; import { shouldShowChatFocusHint, useChatInputContainer } from "./use-chat-input-container"; import type { ChatInputContainerHandle } from "./chat-input-container"; function renderInputState(overrides: Partial[0]> = {}) { return renderHook( () => useChatInputContainer({ ref: createRef(), sessionId: "session-1", isSending: false, isStarting: false, isPreparingEnvironment: false, isMoving: false, isFailed: false, needsRecovery: false, executorUnavailable: false, isAgentBusy: false, hasAgentCommands: true, placeholder: undefined, contextItems: [], pendingClarification: null, onClarificationResolved: undefined, pendingCommentsByFile: undefined, hasContextComments: false, showRequestChangesTooltip: false, onRequestChangesTooltipDismiss: undefined, onSubmit: vi.fn(), ...overrides, }), { wrapper: ({ children }) => React.createElement(ToastProvider, null, children), }, ); } describe("useChatInputContainer", () => { it("disables the editor while the session is still STARTING", () => { // The editor must stay uneditable until the agent reaches RUNNING — if // the user can press Cmd+Enter mid-startup, the backend rejects with // "Failed to send message to agent" because the agent process isn't // ready yet. This is the regression from earlier rounds where the e2e // quick-chat suite kept failing on race conditions. const { result } = renderInputState({ isStarting: true }); expect(result.current.isDisabled).toBe(true); expect(result.current.submitDisabled).toBe(true); expect(result.current.submitDisabledReason).toBeUndefined(); }); it("surfaces the setup tooltip only while a container/sandbox is preparing", () => { const { result } = renderInputState({ isStarting: true, isPreparingEnvironment: true, }); expect(result.current.submitDisabledReason).toBe("The agent is still being set up."); }); it("keeps queueing enabled for an interactive clarification during stale startup", () => { const { result } = renderInputState({ isStarting: true, isPreparingEnvironment: true, placeholder: "Queue instructions while the question is pending...", pendingClarification: { id: "clarification-1" } as never, onClarificationResolved: vi.fn(), }); expect(result.current.isDisabled).toBe(false); expect(result.current.submitDisabled).toBe(false); expect(result.current.submitDisabledReason).toBeUndefined(); expect(result.current.inputPlaceholder).toBe( "Queue instructions while the question is pending...", ); }); }); describe("shouldShowChatFocusHint", () => { it("hides the hint when a blurred editor has draft text", () => { expect( shouldShowChatFocusHint({ isInputFocused: false, value: "halle from chat input", hasClarification: false, hasPendingComments: false, }), ).toBe(false); }); it("shows the hint only for an empty blurred editor without blocking overlays", () => { expect( shouldShowChatFocusHint({ isInputFocused: false, value: " ", hasClarification: false, hasPendingComments: false, }), ).toBe(true); expect( shouldShowChatFocusHint({ isInputFocused: true, value: "", hasClarification: false, hasPendingComments: false, }), ).toBe(false); }); });