import { fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import { ExecutionGraphCard, formatExecutionDuration } from '@/pages/Chat/ExecutionGraphCard'; import { getToolVisibilityId } from '@/pages/Chat/chat-transcript'; import type { TaskStep } from '@/pages/Chat/task-visualization'; vi.mock('react-i18next', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key, }), }; }); describe('ExecutionGraphCard', () => { it('shows only duration units that have elapsed', () => { expect(formatExecutionDuration(2_500)).toBe('2s'); expect(formatExecutionDuration(62_500)).toBe('1m 2s'); expect(formatExecutionDuration(3_661_500)).toBe('1h 1m 1s'); expect(formatExecutionDuration(undefined)).toBe('—'); }); it('uses muted styling for the collapsed duration summary', () => { render( , ); const summary = screen .getByTestId('chat-execution-graph-expand') .querySelector('span.truncate'); expect(summary).not.toBeNull(); expect(summary).toHaveClass('text-muted-foreground/70'); expect(summary).not.toHaveClass('text-foreground'); }); it('shows the current process summary while the graph is active', () => { render( , ); expect(screen.getByTestId('chat-execution-graph-collapse')).toHaveTextContent('web_fetch'); }); it('drives the real graph step detail from the shared visibility set', () => { const visibilityId = getToolVisibilityId('msg_assistant', 'tool-read', 0); const onStepExpandedChange = vi.fn(); const step: TaskStep = { id: 'tool-read', visibilityId, label: 'read', status: 'completed', kind: 'tool', detail: '{"file":"README.md"}', depth: 1, }; const { rerender } = render( , ); expect(screen.queryByText(/README\.md/)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /read/i })); expect(onStepExpandedChange).toHaveBeenCalledWith(visibilityId, true); rerender( , ); expect(screen.getByText(/"file": "README\.md"/)).toBeInTheDocument(); }); it('notifies the parent when an uncontrolled active graph auto-collapses', () => { const onExpandedChange = vi.fn(); const step: TaskStep = { id: 'tool-read', label: 'read', status: 'running', kind: 'tool', detail: '{"file":"README.md"}', depth: 1, }; const { rerender } = render( , ); expect(screen.getByTestId('chat-execution-graph')).toHaveAttribute( 'data-collapsed', 'true', ); fireEvent.click(screen.getByTestId('chat-execution-graph-expand')); expect(screen.getByTestId('chat-execution-graph')).toHaveAttribute( 'data-collapsed', 'false', ); rerender( , ); expect(screen.getByTestId('chat-execution-graph')).toHaveAttribute( 'data-collapsed', 'true', ); expect(onExpandedChange).toHaveBeenCalledWith(false); }); it('notifies the parent when a completed uncontrolled graph mounts collapsed', () => { const onExpandedChange = vi.fn(); render( , ); expect(onExpandedChange).toHaveBeenCalledWith(false); }); });