spanDetail.test.tsx5.1 KBView on GitHub /**
* The detail panel. Three things have to hold:
* - a tool-call span shows its name and both payloads,
* - a 200KB payload truncates behind an explicit expand instead of freezing the tab,
* - an execution with no tool calls says so — that is a finding, not a spinner.
*/
import { fireEvent, render, screen, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import type { ExecutionTree } from '@zero/server/execution-tree';
import { SpanDetail } from '@/modules/agentExecutions/components/SpanDetail';
import { buildTraceFixture } from './traceFixture';
function renderDetail(spanId: string, tree: ExecutionTree = buildTraceFixture()) {
const onSelectSpan = jest.fn();
render(
<MemoryRouter>
<SpanDetail tree={tree} spanId={spanId} onSelectSpan={onSelectSpan} />
</MemoryRouter>,
);
return { onSelectSpan };
}
/** The fixture with an argument blob big enough to matter. */
function treeWithOversizedPayload(): ExecutionTree {
const tree = buildTraceFixture();
const node = tree.nodes.find((candidate) => candidate.runId === 'subagent-b')!;
node.toolCalls[0]!.arguments = { blob: 'x'.repeat(50_000) };
return tree;
}
describe('SpanDetail — tool-call span', () => {
it('shows the tool name, arguments and result', () => {
renderDetail('t-b1');
expect(screen.getByTestId('span-detail-tool-name')).toHaveTextContent('search-crm');
expect(
within(screen.getByTestId('payload-Arguments')).getByText(/conversationId/),
).toBeInTheDocument();
expect(
within(screen.getByTestId('payload-Result')).getByText(/CRM timed out/),
).toBeInTheDocument();
});
it('says an unmeasured duration was never recorded instead of printing a confident 0ms', () => {
renderDetail('t-b1');
expect(screen.getByTestId('span-detail-duration')).toHaveTextContent('duration never recorded');
expect(screen.getByTestId('span-detail-point-note')).toHaveTextContent(
/no start time was recorded|never measured/i,
);
// The only timestamp such a row has is when it ENDED — say that, not "started".
expect(screen.getByTestId('span-detail-point-note')).toHaveTextContent('Finished at +900ms');
});
it('shows the real elapsed time for a measured tool call', () => {
renderDetail('t-c1');
expect(screen.getByTestId('span-detail-duration')).toHaveTextContent('900ms');
expect(screen.getByTestId('span-detail-tool-duration')).toHaveTextContent('900ms');
// A measured row has a genuine start, so the header dates it from there.
expect(screen.getByTestId('span-detail')).toHaveTextContent('started +5.6s');
expect(screen.queryByTestId('span-detail-point-note')).not.toBeInTheDocument();
});
it('reports a measured 0ms call as 0ms, not as unrecorded', () => {
renderDetail('t-c4');
// The discriminator again: `durationMs === 0` is a real answer here.
expect(screen.getByTestId('span-detail-duration')).toHaveTextContent('0ms');
expect(screen.getByTestId('span-detail-duration')).not.toHaveTextContent('never recorded');
expect(screen.queryByTestId('span-detail-point-note')).not.toBeInTheDocument();
});
it('truncates an oversized payload behind an explicit expand', () => {
renderDetail('t-b1', treeWithOversizedPayload());
const payload = screen.getByTestId('payload-Arguments');
const notice = screen.getByTestId('payload-truncated-Arguments');
expect(notice).toHaveTextContent(/Truncated/);
const rendered = within(payload).getByText(/xxxx/);
expect(rendered.textContent!.length).toBeLessThan(10_000);
fireEvent.click(within(notice).getByRole('button', { name: /show full payload/i }));
expect(screen.queryByTestId('payload-truncated-Arguments')).not.toBeInTheDocument();
expect(within(payload).getByText(/xxxx/).textContent!.length).toBeGreaterThan(49_000);
});
});
describe('SpanDetail — execution span', () => {
it('shows agent, status, conversation link, and jumps to children', () => {
const { onSelectSpan } = renderDetail('review-a');
expect(screen.getByTestId('span-detail-agent-name')).toHaveTextContent('review-conversation-a');
expect(screen.getByTestId('span-detail-status')).toHaveTextContent('completed');
expect(screen.getByTestId('span-detail-conversation-link')).toHaveAttribute(
'href',
'/conversations/c/conv-a',
);
fireEvent.click(screen.getByRole('button', { name: /draft-email/ }));
expect(onSelectSpan).toHaveBeenCalledWith('subagent-a');
});
it('shows the empty state for an execution that recorded no tool calls', () => {
renderDetail('review-a');
const empty = screen.getByTestId('span-detail-no-tool-calls');
expect(empty).toHaveTextContent('No tool calls recorded');
expect(screen.queryByRole('status')).not.toBeInTheDocument();
});
it('prompts to pick a span when nothing is selected', () => {
const onSelectSpan = jest.fn();
render(
<MemoryRouter>
<SpanDetail tree={buildTraceFixture()} spanId={null} onSelectSpan={onSelectSpan} />
</MemoryRouter>,
);
expect(screen.getByTestId('span-detail-empty-selection')).toBeInTheDocument();
});
});