cell.spec.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // @vitest-environment jsdom
  2. /**
  3. * TrajectoryCell presentation: kind tags, ellipsis-hosting text, Message
  4. * metric columns, own-duration formatting, and selected ring.
  5. */
  6. import { afterEach, describe, expect, it } from 'vitest'
  7. import { cleanup, render, screen } from '@testing-library/react'
  8. import {
  9. formatElapsedSeconds,
  10. TrajectoryCell,
  11. type TrajectoryCellKind,
  12. } from '../src/client/TrajectoryCell.tsx'
  13. import { formatDurationMillis } from '../src/client/trajectory-record.ts'
  14. afterEach(cleanup)
  15. describe('formatDurationMillis', () => {
  16. it('formats exact millisecond labels with thousands separators', () => {
  17. expect(formatDurationMillis(0)).toBe('0 ms')
  18. expect(formatDurationMillis(29)).toBe('29 ms')
  19. expect(formatDurationMillis(500)).toBe('500 ms')
  20. expect(formatDurationMillis(1_500)).toBe('1,500 ms')
  21. expect(formatDurationMillis(235_200)).toBe('235,200 ms')
  22. expect(formatDurationMillis(null)).toBe('—')
  23. expect(formatDurationMillis(Number.NaN)).toBe('—')
  24. })
  25. })
  26. describe('formatElapsedSeconds', () => {
  27. it('formats known durations and uses an em dash when absent', () => {
  28. expect(formatElapsedSeconds(null)).toBe('—')
  29. expect(formatElapsedSeconds(235)).toBe('235,000 ms')
  30. expect(formatElapsedSeconds(235.0)).toBe('235,000 ms')
  31. expect(formatElapsedSeconds(235.2)).toBe('235,200 ms')
  32. expect(formatElapsedSeconds(235.25)).toBe('235,250 ms')
  33. expect(formatElapsedSeconds(0)).toBe('0 ms')
  34. expect(formatElapsedSeconds(0.029)).toBe('29 ms')
  35. expect(formatElapsedSeconds(0.5)).toBe('500 ms')
  36. expect(formatElapsedSeconds(1.5)).toBe('1,500 ms')
  37. expect(formatElapsedSeconds(Number.NaN)).toBe('—')
  38. })
  39. })
  40. describe('TrajectoryCell', () => {
  41. it('renders index, kind tag, text, and time for a Tool row', () => {
  42. render(
  43. <TrajectoryCell
  44. index={6}
  45. kind="tool"
  46. text="bash · Read src/index.ts"
  47. timeSeconds={5}
  48. />,
  49. )
  50. expect(screen.getByText('#6')).toBeTruthy()
  51. expect(screen.getByText('Tool')).toBeTruthy()
  52. expect(screen.getByText('bash · Read src/index.ts')).toBeTruthy()
  53. expect(screen.getByText('5,000 ms')).toBeTruthy()
  54. })
  55. it('Message rows expose Input / Output / Think metric columns before time', () => {
  56. const { container } = render(
  57. <TrajectoryCell
  58. index={3}
  59. kind="message"
  60. text="Let me now read the actual source files to understa..."
  61. timeSeconds={235.2}
  62. input={136}
  63. output={381}
  64. think={155}
  65. />,
  66. )
  67. expect(screen.getByText('Message')).toBeTruthy()
  68. expect(screen.getByText('136')).toBeTruthy()
  69. expect(screen.getByText('381')).toBeTruthy()
  70. expect(screen.getByText('155')).toBeTruthy()
  71. expect(screen.getByText('235,200 ms')).toBeTruthy()
  72. const texts = [...container.querySelectorAll('span')].map(el => el.textContent)
  73. expect(texts.indexOf('136')).toBeLessThan(texts.indexOf('381'))
  74. expect(texts.indexOf('381')).toBeLessThan(texts.indexOf('155'))
  75. expect(texts.indexOf('155')).toBeLessThan(texts.indexOf('235,200 ms'))
  76. })
  77. it('selected marks the row for the brand-primary inset ring', () => {
  78. const { container } = render(
  79. <TrajectoryCell index={15} kind="message" text="pictur..." timeSeconds={123.6} selected />,
  80. )
  81. expect(container.firstElementChild?.getAttribute('data-selected')).toBe('true')
  82. })
  83. it.each([
  84. ['user', 'User'],
  85. ['tool', 'Tool'],
  86. ] as const)('kind %s shows the %s tag and no metric columns', (kind: TrajectoryCellKind, label: string) => {
  87. const { container } = render(
  88. <TrajectoryCell index={1} kind={kind} text="summary" timeSeconds={kind === 'user' ? 0 : null} input={1} output={2} think={3} />,
  89. )
  90. expect(screen.getByText(label)).toBeTruthy()
  91. expect(container.querySelector('[data-kind]')?.getAttribute('data-kind')).toBe(kind)
  92. expect(screen.queryByText('1')).toBeNull()
  93. expect(screen.queryByText('2')).toBeNull()
  94. expect(screen.queryByText('3')).toBeNull()
  95. })
  96. })