import "@testing-library/jest-dom"; import React from "react"; import { act, render, screen } from "@testing-library/react"; import { GlobalChatbox } from "./GlobalChatbox"; const createSession = jest.fn(); const mockFetchAgentRuntimeHealth = jest.fn(); const mockFetchAgentModels = jest.fn(); let mockCurrentProjectId = "project-1"; jest.mock("@refinedev/core", () => ({ useNotification: () => ({ open: jest.fn() }), })); jest.mock("@/lib/chatModels", () => ({ fetchAgentModels: (...args: unknown[]) => mockFetchAgentModels(...args), })); jest.mock("@/lib/agentRuntime", () => ({ fetchAgentRuntimeHealth: (...args: unknown[]) => mockFetchAgentRuntimeHealth(...args), })); jest.mock("@/store/projectStore", () => ({ useProjectStore: (selector: (state: { currentProjectId: string }) => unknown) => selector({ currentProjectId: mockCurrentProjectId }), })); jest.mock("./globalChatboxVoice", () => ({ useSpeechSynthesis: () => ({ speechState: "idle", speakingMessageId: null, speak: jest.fn(), pause: jest.fn(), resume: jest.fn(), stop: jest.fn(), isSupported: true, }), useSpeechRecognition: () => ({ isListening: false, start: jest.fn(), stop: jest.fn(), isSupported: true, }), })); jest.mock("./hooks/useAgentToolActions", () => ({ useAgentToolActions: () => jest.fn(), })); jest.mock("./hooks/useAgentChatSession", () => ({ useAgentChatSession: () => ({ messages: [], chatSessions: [], activeSessionId: undefined, isHydrating: false, loadingSessionId: null, isStreaming: false, sessionTitle: "新会话", sendPrompt: jest.fn(), createBranch: jest.fn(), abort: jest.fn(), replyPermission: jest.fn(), replyQuestion: jest.fn(), rejectQuestion: jest.fn(), createSession, renameSession: jest.fn(), removeSession: jest.fn(), switchSession: jest.fn(), }), })); jest.mock("./AgentHeader", () => ({ AgentHeader: () =>
Agent header
, })); jest.mock("./AgentHistoryPanel", () => ({ AgentHistoryPanel: () =>
History
, })); jest.mock("./AgentWorkspace", () => ({ AgentWorkspace: ({ runtimeState }: { runtimeState: string }) => (
Workspace state: {runtimeState}
), })); jest.mock("./AgentComposer", () => ({ AgentComposer: React.forwardRef(function MockAgentComposer(props: { approvalMode: string }, _ref) { return
Composer mode: {props.approvalMode}
; }), })); jest.mock("./GlobalChatboxParts", () => ({ Blob: () => null, })); describe("GlobalChatbox lifecycle", () => { beforeEach(() => { jest.useFakeTimers(); createSession.mockClear(); mockFetchAgentRuntimeHealth.mockReset(); mockFetchAgentRuntimeHealth.mockImplementation(() => new Promise(() => {})); mockFetchAgentModels.mockReset(); mockFetchAgentModels.mockImplementation(() => new Promise(() => {})); mockCurrentProjectId = "project-1"; }); afterEach(() => { jest.runOnlyPendingTimers(); jest.useRealTimers(); jest.restoreAllMocks(); }); it("keeps content mounted and preserves the session across close and reopen", async () => { const { rerender } = render(); act(() => jest.runOnlyPendingTimers()); expect(createSession).toHaveBeenCalledTimes(1); expect(screen.getByTestId("agent-workspace")).toBeInTheDocument(); rerender(); act(() => jest.advanceTimersByTime(300)); expect(screen.getByTestId("agent-workspace")).toBeInTheDocument(); rerender(); act(() => jest.runOnlyPendingTimers()); expect(createSession).toHaveBeenCalledTimes(1); mockCurrentProjectId = "project-2"; rerender(); act(() => jest.runOnlyPendingTimers()); expect(createSession).toHaveBeenCalledTimes(2); }); it("defaults the composer to automatic permission approval", () => { render(); expect(screen.getByText("Composer mode: auto")).toBeInTheDocument(); }); it("passes backend startup failures into the existing workspace empty state", async () => { mockFetchAgentRuntimeHealth.mockResolvedValueOnce(false); render(); await act(async () => Promise.resolve()); expect(screen.getByText("Workspace state: unavailable")).toBeInTheDocument(); }); it("reserves models unavailable for an empty model configuration", async () => { mockFetchAgentRuntimeHealth.mockResolvedValueOnce(true); mockFetchAgentModels.mockResolvedValueOnce({ models: [] }); render(); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByText("Workspace state: models_unavailable")).toBeInTheDocument(); }); it("reports an auth dependency failure as unavailable and retries quickly", async () => { jest.spyOn(console, "error").mockImplementation(() => undefined); mockFetchAgentRuntimeHealth.mockResolvedValue(true); mockFetchAgentModels .mockRejectedValueOnce(new Error("authentication service unavailable")) .mockResolvedValueOnce({ defaultModel: "provider/model", models: [{ id: "provider/model", label: "Model" }], }); render(); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByText("Workspace state: unavailable")).toBeInTheDocument(); expect(mockFetchAgentModels).toHaveBeenCalledTimes(1); await act(async () => { jest.advanceTimersByTime(5_000); await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByText("Workspace state: ready")).toBeInTheDocument(); expect(mockFetchAgentModels).toHaveBeenCalledTimes(2); }); });