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