import type { IChatItem } from '@/app/components/base/chat/chat/type' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { useClickAway } from 'ahooks' import { fetchAgentLogDetail } from '@/service/log' import AgentLogModal from '../index' const { mockToast } = vi.hoisted(() => { const mockToast = Object.assign(vi.fn(), { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn(), dismiss: vi.fn(), update: vi.fn(), promise: vi.fn(), }) return { mockToast } }) vi.mock('@/service/log', () => ({ fetchAgentLogDetail: vi.fn(), })) vi.mock('@/app/components/base/ui/toast', () => ({ toast: mockToast, })) vi.mock('@/app/components/app/store', () => ({ useStore: vi.fn(selector => selector({ appDetail: { id: 'app-id' } })), })) vi.mock('@/app/components/workflow/run/status', () => ({ default: ({ status, time, tokens, error }: { status: string, time?: number, tokens?: number, error?: string }) => (
{error ? {String(error)} : null}
), })) vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({ default: ({ title, value }: { title: React.ReactNode, value: string | object }) => (
{title} {typeof value === 'string' ? value : JSON.stringify(value)}
), })) vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ formatTime: (ts: number, fmt: string) => `${ts}-${fmt}` }), })) vi.mock('@/app/components/workflow/block-icon', () => ({ default: () =>
, })) vi.mock('@/app/components/base/icons/src/vender/line/arrows', () => ({ ChevronRight: (props: { className?: string }) =>
, })) vi.mock('ahooks', () => ({ useClickAway: vi.fn(), })) const mockLog = { id: 'msg-id', conversationId: 'conv-id', content: 'content', isAnswer: false, input: 'test input', } as IChatItem const mockProps = { currentLogItem: mockLog, width: 1000, onCancel: vi.fn(), } describe('AgentLogModal', () => { beforeEach(() => { vi.clearAllMocks() vi.mocked(fetchAgentLogDetail).mockResolvedValue({ meta: { status: 'succeeded', executor: 'User', start_time: '2023-01-01', elapsed_time: 1.0, total_tokens: 100, agent_mode: 'function_call', iterations: 1, }, iterations: [{ created_at: '', files: [], thought: '', tokens: 0, tool_raw: { inputs: '', outputs: '' }, tool_calls: [{ tool_name: 'tool1', status: 'success', tool_icon: null, tool_label: { 'en-US': 'Tool 1' } }], }], files: [], }) }) it('should return null if no currentLogItem', () => { const { container } = render() expect(container.firstChild).toBeNull() }) it('should return null if no conversationId', () => { const { container } = render() expect(container.firstChild).toBeNull() }) it('should render correctly when log item is provided', async () => { render() expect(screen.getByText('appLog.runDetail.workflowTitle')).toBeInTheDocument() await waitFor(() => { expect(screen.getByText(/runLog.detail/i)).toBeInTheDocument() }) }) it('should call onCancel when close button is clicked', () => { vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) render() const closeBtn = screen.getByRole('heading', { name: /appLog.runDetail.workflowTitle/i }).nextElementSibling! fireEvent.click(closeBtn) expect(mockProps.onCancel).toHaveBeenCalledTimes(1) }) it('should call onCancel when clicking away', () => { vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) let clickAwayHandler!: (event: Event) => void vi.mocked(useClickAway).mockImplementation((callback) => { clickAwayHandler = callback }) render() clickAwayHandler(new Event('click')) expect(mockProps.onCancel).toHaveBeenCalledTimes(1) }) it('should ignore click-away before mounted state is set', () => { vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) let invoked = false vi.mocked(useClickAway).mockImplementation((callback) => { if (!invoked) { invoked = true callback(new Event('click')) } }) render() expect(mockProps.onCancel).not.toHaveBeenCalled() }) })