tool-row-styles.client.spec.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. /**
  2. * The one-line contract of the ToolRow summary line as CSS text. jsdom has no
  3. * layout, so the rendering specs (chat-tool-row.spec.tsx) can pin which spans
  4. * exist but not whether a narrow row still fits on one line; these read the
  5. * declarations the layout depends on.
  6. */
  7. import { readFileSync } from 'node:fs'
  8. import { fileURLToPath } from 'node:url'
  9. import { describe, expect, it } from 'vitest'
  10. const css = readFileSync(fileURLToPath(new URL('../src/client/tool/components/ToolRow.module.css', import.meta.url)), 'utf8')
  11. /** Declarations only: the sheet's prose names the properties it explains. */
  12. const declarationText = css.replace(/\/\*[\s\S]*?\*\//g, ' ')
  13. function declarations(selector: string): string[] {
  14. // Anchored at a rule boundary: an unanchored match would silently read a
  15. // compound rule that merely contains the selector (`.root:hover .summarySuffix`)
  16. // if one ever lands above the base rule.
  17. const rule = new RegExp(`(?:^|\\})\\s*\\${selector}\\s*\\{([^{}]*)\\}`).exec(declarationText)
  18. if (rule === null) throw new Error(`ToolRow.module.css has no \`${selector}\` rule`)
  19. return (rule[1] ?? '').split(';').map(part => part.trim()).filter(Boolean)
  20. }
  21. describe('ToolRow.module.css summary line', () => {
  22. it('keeps the summary suffix on one line and unshrunk', () => {
  23. // `flex: none` stops the box shrinking, not the text wrapping: without
  24. // `nowrap`, a row too narrow for title + separator + suffix wraps the `+n`
  25. // onto a second line — the exact case the slot exists to survive.
  26. expect(declarations('.summarySuffix')).toEqual(expect.arrayContaining([
  27. 'flex: none',
  28. 'white-space: nowrap',
  29. ]))
  30. })
  31. it('leaves the truncation to the summary text alone', () => {
  32. // The suffix must never ellipsize: a clipped count reads as a smaller
  33. // number rather than as missing information.
  34. expect(declarations('.summary')).toEqual(expect.arrayContaining([
  35. 'overflow: hidden',
  36. 'text-overflow: ellipsis',
  37. 'white-space: nowrap',
  38. ]))
  39. expect(declarations('.summarySuffix')).not.toEqual(expect.arrayContaining(['text-overflow: ellipsis']))
  40. })
  41. it('sizes the summary texts from the secondary content tier', () => {
  42. // The Settings font-size preference must reach tool-call rows, not only
  43. // the narration body: summary, suffix, and file link read the secondary
  44. // tier (one step under the body), matching think text.
  45. for (const selector of ['.summary', '.summarySuffix', '.fileLink']) {
  46. expect(declarations(selector)).toEqual(expect.arrayContaining([
  47. 'font-size: var(--dsh-content-font-size-secondary, 13px)',
  48. 'line-height: calc(24px + var(--dsh-content-font-delta, 0px))',
  49. ]))
  50. }
  51. })
  52. })