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();
});
});