import { render, screen, fireEvent, within } from '@testing-library/react'; import { ExecutionTimeline } from '../ExecutionTimeline'; import type { NodeExecView } from '../types'; const nodes: NodeExecView[] = [ { nodeId: 'a', name: 'Fetch data', type: 'trigger.event', status: 'success', executionOrder: 0, executionTimeMs: 1234, }, { nodeId: 'b', name: 'Transform', type: 'transform.set', status: 'running', executionOrder: 1, }, { nodeId: 'c', name: 'Notify', type: 'action.http', status: 'error', executionOrder: 2, executionTimeMs: 42, error: 'Connection refused', }, ]; describe('ExecutionTimeline', () => { it('renders one row per node execution, in order', () => { render(); const rows = screen.getAllByRole('listitem'); expect(rows).toHaveLength(3); expect(rows[0]).toHaveTextContent('Fetch data'); expect(rows[1]).toHaveTextContent('Transform'); expect(rows[2]).toHaveTextContent('Notify'); }); it('renders a status badge per node with the correct label', () => { render(); expect(screen.getByText('Success')).toBeInTheDocument(); expect(screen.getByText('Running')).toBeInTheDocument(); expect(screen.getByText('Error')).toBeInTheDocument(); }); it('renders human-readable durations', () => { render(); expect(screen.getByText('1.23s')).toBeInTheDocument(); expect(screen.getByText('42ms')).toBeInTheDocument(); }); it('renders inline error text for error nodes', () => { render(); expect(screen.getByText('Connection refused')).toBeInTheDocument(); }); it('shows a live/pulsing indicator only for the running node', () => { const { container } = render(); const live = container.querySelectorAll('[data-live="true"]'); expect(live).toHaveLength(1); const rows = screen.getAllByRole('listitem'); // running node is the second row (b) expect(within(rows[1]).getByText('Running')).toBeInTheDocument(); expect(rows[1].querySelector('[data-live="true"]')).not.toBeNull(); }); it('does not show a live indicator for terminal statuses', () => { const terminal: NodeExecView[] = [ { nodeId: 'a', name: 'A', type: 't', status: 'success', executionOrder: 0 }, { nodeId: 'b', name: 'B', type: 't', status: 'error', executionOrder: 1 }, { nodeId: 'c', name: 'C', type: 't', status: 'skipped', executionOrder: 2 }, ]; const { container } = render(); expect(container.querySelectorAll('[data-live="true"]')).toHaveLength(0); }); it('calls onSelectNode when a row is clicked', () => { const onSelectNode = jest.fn(); render(); fireEvent.click(screen.getByText('Transform')); expect(onSelectNode).toHaveBeenCalledWith('b'); }); it('marks the active node row as selected', () => { render(); const rows = screen.getAllByRole('listitem'); expect(rows[2]).toHaveAttribute('aria-current', 'true'); expect(rows[0]).not.toHaveAttribute('aria-current', 'true'); }); it('renders an empty state when there are no executions', () => { render(); expect(screen.queryAllByRole('listitem')).toHaveLength(0); expect(screen.getByText('No node executions')).toBeInTheDocument(); }); });