traceWaterfall.test.tsx12.2 KBView on GitHub /**
* The waterfall's load-bearing claims:
* - rows nest by depth and order by start time,
* - every bar is positioned against the ROOT window (so siblings compare),
* - overlapping siblings actually overlap on screen (parallel reads as parallel),
* - a sub-pixel span is still clickable, and a span whose duration was never
* recorded is drawn as a point rather than a bar with an invented width,
* - a MEASURED tool call is a real bar, and a measured 0ms call is drawn
* differently from an unmeasured one — `timingSource` decides, not a zero
* duration,
* - an errored span is visibly distinct and its ancestor path is open on load,
* - collapsing hides exactly the subtree.
*/
import { fireEvent, render, screen, within } from '@testing-library/react';
import { useState } from 'react';
import { TraceWaterfall } from '@/modules/agentExecutions/components/TraceWaterfall';
import { MIN_BAR_PX, POINT_MARKER_PX } from '@/modules/agentExecutions/constants';
import type { TraceFilters } from '@/modules/agentExecutions/types';
import { clearCollapsedSession } from '@/modules/agentExecutions/utils/collapseSession';
import { buildTraceFixture, pct } from './traceFixture';
function Harness({ filters }: { filters?: TraceFilters } = {}) {
const [tree] = useState(buildTraceFixture);
const [selected, setSelected] = useState<string | null>(null);
return (
<TraceWaterfall
tree={tree}
filters={filters}
selectedSpanId={selected}
onSelectSpan={setSelected}
/>
);
}
beforeEach(() => {
clearCollapsedSession();
});
describe('TraceWaterfall — layout', () => {
it('nests spans by depth and orders siblings by start time', () => {
render(<Harness />);
const rows = screen.getAllByRole('treeitem');
const ids = rows.map((row) => row.getAttribute('data-span-id'));
// review-b starts at +150 but review-d at +9000: start order, not id order.
expect(ids).toEqual([
'root',
'review-a',
'subagent-a',
'review-b',
'subagent-b',
't-b1',
'review-c',
't-c1',
't-c3',
't-c4',
't-c2',
'review-d',
]);
expect(screen.getByTestId('span-row-root')).toHaveAttribute('aria-level', '1');
expect(screen.getByTestId('span-row-review-a')).toHaveAttribute('aria-level', '2');
expect(screen.getByTestId('span-row-subagent-a')).toHaveAttribute('aria-level', '3');
expect(screen.getByTestId('span-row-t-b1')).toHaveAttribute('aria-level', '4');
});
it('computes bar offset and width against the root window', () => {
render(<Harness />);
// Root window is 10000ms, so 4000ms of duration is 40% of the axis.
const reviewA = screen.getByTestId('span-bar-review-a');
expect(pct(reviewA.style.left)).toBeCloseTo(1); // +100ms of 10000ms
expect(pct(reviewA.style.width)).toBeCloseTo(40); // 4000ms of 10000ms
// A nested span is scaled to the ROOT, not to its own parent.
const subagentA = screen.getByTestId('span-bar-subagent-a');
expect(pct(subagentA.style.left)).toBeCloseTo(2);
expect(pct(subagentA.style.width)).toBeCloseTo(30);
});
it('renders overlapping siblings as overlapping bars', () => {
render(<Harness />);
const a = screen.getByTestId('span-bar-review-a');
const b = screen.getByTestId('span-bar-review-b');
const aStart = pct(a.style.left);
const aEnd = aStart + pct(a.style.width);
const bStart = pct(b.style.left);
const bEnd = bStart + pct(b.style.width);
// b begins before a ends: on a shared axis that is visible parallelism.
expect(bStart).toBeLessThan(aEnd);
expect(aStart).toBeLessThan(bEnd);
// review-d is the staircase case — it starts after both siblings finish.
const d = screen.getByTestId('span-bar-review-d');
expect(pct(d.style.left)).toBeGreaterThan(aEnd);
expect(pct(d.style.left)).toBeGreaterThan(bEnd);
});
it('floors a sub-pixel span at the minimum clickable width', () => {
render(<Harness />);
// 2ms inside a 10s trace is 0.02% — far below one pixel at any sane width.
const bar = screen.getByTestId('span-bar-review-d');
expect(pct(bar.style.width)).toBeLessThan(0.1);
expect(bar.style.minWidth).toBe(`${MIN_BAR_PX}px`);
expect(bar).toHaveAttribute('data-span-shape', 'bar');
});
it('draws an unmeasured tool call as a point, not as a bar with an invented duration', () => {
render(<Harness />);
const bar = screen.getByTestId('span-bar-t-b1');
expect(bar).toHaveAttribute('data-span-shape', 'point');
expect(bar.style.width).toBe(`${POINT_MARKER_PX}px`);
expect(screen.getByTestId('span-duration-t-b1')).toHaveTextContent('—');
});
it('marks dead time inside the root that no child accounts for', () => {
render(<Harness />);
// Children cover 100→5150, 5500→8500 and 9000→9002. What is left is
// 5150→5500, 8500→9000 and the tail — the root's un-instrumented time.
const first = screen.getByTestId('span-gap-root-0');
expect(pct(first.style.left)).toBeCloseTo(51.5);
expect(pct(first.style.width)).toBeCloseTo(3.5);
const second = screen.getByTestId('span-gap-root-1');
expect(pct(second.style.left)).toBeCloseTo(85);
expect(pct(second.style.width)).toBeCloseTo(5);
});
it('shows a ruler of relative offsets', () => {
render(<Harness />);
const ruler = screen.getByTestId('trace-ruler');
expect(within(ruler).getByText('+0ms')).toBeInTheDocument();
expect(within(ruler).getByText('+10.0s')).toBeInTheDocument();
});
});
/**
* `agent_tool_calls.started_at` is nullable forever, so instrumented and
* historical rows share every real trace. The viewer has to tell one story per
* row without ever guessing from a zero duration.
*/
describe('TraceWaterfall — tool-call timing', () => {
it('renders a measured tool call as a real bar scaled to the root window', () => {
render(<Harness />);
// t-c1 ran 5600 → 6500 inside a 10000ms root window.
const bar = screen.getByTestId('span-bar-t-c1');
expect(bar).toHaveAttribute('data-span-shape', 'bar');
expect(pct(bar.style.left)).toBeCloseTo(56);
expect(pct(bar.style.width)).toBeCloseTo(9);
expect(bar.style.minWidth).toBe(`${MIN_BAR_PX}px`);
expect(screen.getByTestId('span-duration-t-c1')).toHaveTextContent('900ms');
});
it('floors a very short measured call at the minimum clickable width', () => {
render(<Harness />);
// 3ms of a 10s trace is 0.03% — invisible and unclickable without the floor.
const bar = screen.getByTestId('span-bar-t-c3');
expect(bar).toHaveAttribute('data-span-shape', 'bar');
expect(pct(bar.style.width)).toBeLessThan(0.1);
expect(bar.style.minWidth).toBe(`${MIN_BAR_PX}px`);
});
it('draws a measured 0ms call differently from one that was never measured', () => {
render(<Harness />);
// Both carry `durationMs: 0`. Only `timingSource` separates "it was instant"
// from "we have no idea", and the two must not collapse into one mark.
const measured = screen.getByTestId('span-bar-t-c4');
const unmeasured = screen.getByTestId('span-bar-t-c2');
expect(measured).toHaveAttribute('data-span-shape', 'bar');
expect(measured.style.minWidth).toBe(`${MIN_BAR_PX}px`);
expect(screen.getByTestId('span-duration-t-c4')).toHaveTextContent('0ms');
expect(unmeasured).toHaveAttribute('data-span-shape', 'point');
expect(unmeasured.style.width).toBe(`${POINT_MARKER_PX}px`);
expect(screen.getByTestId('span-duration-t-c2')).toHaveTextContent('—');
});
it('says an unmeasured duration was never recorded rather than implying zero', () => {
render(<Harness />);
expect(screen.getByTestId('span-duration-t-c2').title).toMatch(
/never recorded|duration is unknown/i,
);
expect(screen.getByTestId('span-bar-t-c2').title).toMatch(
/never recorded|duration is unknown/i,
);
// And the marker is honestly labelled as the moment it FINISHED.
expect(screen.getByTestId('span-track-t-c2').title).toMatch(/^finished \+7\.5s/);
});
it('renders measured and unmeasured tool calls correctly in one trace', () => {
render(<Harness />);
const shapes = ['t-c1', 't-c3', 't-c4', 't-c2', 't-b1'].map((id) => [
id,
screen.getByTestId(`span-bar-${id}`).getAttribute('data-span-shape'),
]);
expect(shapes).toEqual([
['t-c1', 'bar'],
['t-c3', 'bar'],
['t-c4', 'bar'],
['t-c2', 'point'],
['t-b1', 'point'],
]);
});
it('orders measured tool calls by their real start, not by when they finished', () => {
render(<Harness />);
const ids = screen
.getAllByRole('treeitem')
.map((row) => row.getAttribute('data-span-id'))
.filter((id) => id?.startsWith('t-c'));
// t-c1 starts at 5600 and finishes at 6500; t-c3 starts at 5700 and finishes
// at 5703. Sorting on completion — which is all `created_at` ever gave —
// would put t-c3 first. It starts second, so it renders second.
expect(ids).toEqual(['t-c1', 't-c3', 't-c4', 't-c2']);
});
it('counts a measured tool call as coverage and an unmeasured one as none', () => {
render(<Harness />);
// review-c runs 5500 → 8500. Its measured calls cover 5600 → 6500; t-c4
// (0ms) and t-c2 (unmeasured) cover nothing, so the dead time runs right
// across both of them to the end of the parent.
const gap = screen.getByTestId('span-gap-review-c-0');
expect(pct(gap.style.left)).toBeCloseTo(65);
expect(pct(gap.style.width)).toBeCloseTo(20);
// Exactly one gap: the measured bar really did account for its own window.
expect(screen.queryByTestId('span-gap-review-c-1')).not.toBeInTheDocument();
});
it('does not invent dead time when a filter hides a covering tool call', () => {
// Filtering to t-c3 hides t-c1, whose 5600 → 6500 bar is the only real
// coverage review-c has. A gap computed over the filtered rows would open at
// 5703 and claim ~2.8s of thinking that never happened.
render(<Harness filters={{ toolName: 'tiny-lookup', erroredOnly: false, kinds: [] }} />);
expect(screen.queryByTestId('span-row-t-c1')).not.toBeInTheDocument();
const gap = screen.getByTestId('span-gap-review-c-0');
expect(pct(gap.style.left)).toBeCloseTo(65);
expect(pct(gap.style.width)).toBeCloseTo(20);
});
});
describe('TraceWaterfall — errors and collapsing', () => {
it('makes an errored span visibly distinct and opens the path to it on load', () => {
render(<Harness />);
const errored = screen.getByTestId('span-row-subagent-b');
expect(errored).toHaveAttribute('data-span-error', 'true');
expect(screen.getByTestId('span-error-icon-subagent-b')).toBeInTheDocument();
expect(screen.getByTestId('span-bar-subagent-b').className).toContain('bg-destructive');
// Depth-2 subtrees start folded — except the path to the first error, which
// is expanded so its failing tool call is on screen without a click.
expect(screen.getByTestId('span-row-t-b1')).toBeInTheDocument();
expect(screen.queryByTestId('span-row-t-a1')).not.toBeInTheDocument();
expect(screen.getByTestId('span-row-subagent-b')).toHaveAttribute('aria-expanded', 'true');
expect(screen.getByTestId('span-row-subagent-a')).toHaveAttribute('aria-expanded', 'false');
});
it('collapsing a subtree hides exactly its descendants', () => {
render(<Harness />);
fireEvent.click(screen.getByTestId('span-toggle-review-b'));
expect(screen.queryByTestId('span-row-subagent-b')).not.toBeInTheDocument();
expect(screen.queryByTestId('span-row-t-b1')).not.toBeInTheDocument();
// Everything outside that subtree is untouched.
expect(screen.getByTestId('span-row-review-b')).toBeInTheDocument();
expect(screen.getByTestId('span-row-root')).toBeInTheDocument();
expect(screen.getByTestId('span-row-review-a')).toBeInTheDocument();
expect(screen.getByTestId('span-row-subagent-a')).toBeInTheDocument();
expect(screen.getByTestId('span-row-review-d')).toBeInTheDocument();
});
it('expanding a folded subtree brings back its hidden tool call', () => {
render(<Harness />);
expect(screen.queryByTestId('span-row-t-a1')).not.toBeInTheDocument();
fireEvent.click(screen.getByTestId('span-toggle-subagent-a'));
expect(screen.getByTestId('span-row-t-a1')).toBeInTheDocument();
});
});