dify/web/app/components/base/agent-log-modal/__tests__/index.spec.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

217 lines
5.8 KiB
TypeScript

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('@langgenius/dify-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
}) => (
<div
data-testid="status-panel"
data-status={String(status)}
data-time={String(time)}
data-tokens={String(tokens)}
>
{error ? <span>{String(error)}</span> : null}
</div>
),
}))
vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({
default: ({ title, value }: { title: React.ReactNode; value: string | object }) => (
<div data-testid="code-editor">
{title}
{typeof value === 'string' ? value : JSON.stringify(value)}
</div>
),
}))
vi.mock('@/hooks/use-timestamp', () => ({
default: () => ({ formatTime: (ts: number, fmt: string) => `${ts}-${fmt}` }),
}))
vi.mock('@/app/components/workflow/block-icon', () => ({
default: () => <div data-testid="block-icon" />,
}))
vi.mock('@/app/components/base/icons/src/vender/line/arrows', () => ({
ChevronRight: (props: { className?: string }) => (
<div data-testid="chevron-right" className={props.className} />
),
}))
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(<AgentLogModal {...mockProps} currentLogItem={undefined} />)
expect(container.firstChild).toBeNull()
})
it('should return null if no conversationId', () => {
const { container } = render(
<AgentLogModal {...mockProps} currentLogItem={{ id: '1' } as unknown as IChatItem} />,
)
expect(container.firstChild).toBeNull()
})
it('should render correctly when log item is provided', async () => {
render(<AgentLogModal {...mockProps} />)
expect(screen.getByText('appLog.runDetail.workflowTitle')).toBeInTheDocument()
await waitFor(() => {
expect(screen.getByText(/runLog.detail/i)).toBeInTheDocument()
})
})
it('should render the floating modal through a dialog portal', () => {
vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {}))
const { container } = render(<AgentLogModal {...mockProps} floating />)
const modal = screen.getByRole('dialog')
expect(container).not.toContainElement(modal)
expect(document.body).toContainElement(modal)
expect(modal).toHaveClass('fixed', 'z-50', 'w-[480px]!', 'left-[max(8px,calc(100vw-1136px))]!')
})
it('should call onCancel when close button is clicked', () => {
vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {}))
render(<AgentLogModal {...mockProps} />)
const closeBtn = screen.getByRole('button', { name: 'common.operation.close' })
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(<AgentLogModal {...mockProps} />)
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(<AgentLogModal {...mockProps} />)
expect(mockProps.onCancel).not.toHaveBeenCalled()
})
it('should not use click-away to close the floating dialog', () => {
vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {}))
let clickAwayHandler!: (event: Event) => void
vi.mocked(useClickAway).mockImplementation((callback) => {
clickAwayHandler = callback
})
render(<AgentLogModal {...mockProps} floating />)
clickAwayHandler(new Event('click'))
expect(mockProps.onCancel).not.toHaveBeenCalled()
})
})