formatElapsedSeconds rounded every duration to one decimal, so calls under 50 ms rendered as 0 s. Durations below one second now show integer milliseconds (29 ms); at or above one second the tenth-of-a-second label is unchanged (1.5 s). Step group descriptions reuse the same formatter. The details-panel Duration rows toggle between the readable label and exact milliseconds on click, mirroring the StartedAt toggle; the shared text-selection guard is extracted for both. tabular-nums is dropped from detail values and the toggle buttons because SF's tnum feature widens the decimal point and leaves excessive space after it.
91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
/**
|
|
* TrajectoryCell presentation: kind tags, ellipsis-hosting text, Message
|
|
* metric columns, own-duration formatting, and selected ring.
|
|
*/
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { cleanup, render, screen } from '@testing-library/react'
|
|
import {
|
|
formatElapsedSeconds,
|
|
TrajectoryCell,
|
|
type TrajectoryCellKind,
|
|
} from '../src/client/TrajectoryCell.tsx'
|
|
|
|
afterEach(cleanup)
|
|
|
|
describe('formatElapsedSeconds', () => {
|
|
it('formats known durations and uses an em dash when absent', () => {
|
|
expect(formatElapsedSeconds(null)).toBe('—')
|
|
expect(formatElapsedSeconds(235)).toBe('235 s')
|
|
expect(formatElapsedSeconds(235.0)).toBe('235 s')
|
|
expect(formatElapsedSeconds(235.2)).toBe('235.2 s')
|
|
expect(formatElapsedSeconds(235.25)).toBe('235.3 s')
|
|
expect(formatElapsedSeconds(0)).toBe('0 ms')
|
|
expect(formatElapsedSeconds(0.029)).toBe('29 ms')
|
|
expect(formatElapsedSeconds(0.5)).toBe('500 ms')
|
|
expect(formatElapsedSeconds(1.5)).toBe('1.5 s')
|
|
expect(formatElapsedSeconds(Number.NaN)).toBe('—')
|
|
})
|
|
})
|
|
|
|
describe('TrajectoryCell', () => {
|
|
it('renders index, kind tag, text, and time for a Tool row', () => {
|
|
render(
|
|
<TrajectoryCell
|
|
index={6}
|
|
kind="tool"
|
|
text="bash · Read src/index.ts"
|
|
timeSeconds={5}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('#6')).toBeTruthy()
|
|
expect(screen.getByText('Tool')).toBeTruthy()
|
|
expect(screen.getByText('bash · Read src/index.ts')).toBeTruthy()
|
|
expect(screen.getByText('5 s')).toBeTruthy()
|
|
})
|
|
|
|
it('Message rows expose Input / Output / Think metric columns before time', () => {
|
|
const { container } = render(
|
|
<TrajectoryCell
|
|
index={3}
|
|
kind="message"
|
|
text="Let me now read the actual source files to understa..."
|
|
timeSeconds={235.2}
|
|
input={136}
|
|
output={381}
|
|
think={155}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('Message')).toBeTruthy()
|
|
expect(screen.getByText('136')).toBeTruthy()
|
|
expect(screen.getByText('381')).toBeTruthy()
|
|
expect(screen.getByText('155')).toBeTruthy()
|
|
expect(screen.getByText('235.2 s')).toBeTruthy()
|
|
const texts = [...container.querySelectorAll('span')].map(el => el.textContent)
|
|
expect(texts.indexOf('136')).toBeLessThan(texts.indexOf('381'))
|
|
expect(texts.indexOf('381')).toBeLessThan(texts.indexOf('155'))
|
|
expect(texts.indexOf('155')).toBeLessThan(texts.indexOf('235.2 s'))
|
|
})
|
|
|
|
it('selected marks the row for the brand-primary inset ring', () => {
|
|
const { container } = render(
|
|
<TrajectoryCell index={15} kind="message" text="pictur..." timeSeconds={123.6} selected />,
|
|
)
|
|
expect(container.firstElementChild?.getAttribute('data-selected')).toBe('true')
|
|
})
|
|
|
|
it.each([
|
|
['user', 'User'],
|
|
['tool', 'Tool'],
|
|
] as const)('kind %s shows the %s tag and no metric columns', (kind: TrajectoryCellKind, label: string) => {
|
|
const { container } = render(
|
|
<TrajectoryCell index={1} kind={kind} text="summary" timeSeconds={kind === 'user' ? 0 : null} input={1} output={2} think={3} />,
|
|
)
|
|
expect(screen.getByText(label)).toBeTruthy()
|
|
expect(container.querySelector('[data-kind]')?.getAttribute('data-kind')).toBe(kind)
|
|
expect(screen.queryByText('1')).toBeNull()
|
|
expect(screen.queryByText('2')).toBeNull()
|
|
expect(screen.queryByText('3')).toBeNull()
|
|
})
|
|
})
|