xml-tool-output.spec.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { describe, expect, it } from 'vitest'
  2. import { renderUnknownXml } from '../src/components/xml-tool-output.ts'
  3. const render = (source: string, limit = 4, expanded = false): string[] | undefined => renderUnknownXml(
  4. source,
  5. limit,
  6. expanded,
  7. text => text.replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/gu, control =>
  8. `\\x${control.charCodeAt(0).toString(16).padStart(2, '0')}`),
  9. text => `[label]${text}[/label]`,
  10. count => ` … +${count} lines`,
  11. )
  12. describe('unknown-tool XML rendering', () => {
  13. it('renders nested elements and attributes as an indented tree', () => {
  14. expect(render(`<result>
  15. <path>/tmp/a.txt</path>
  16. <type>file</type>
  17. <content>
  18. <line number="1">hello</line>
  19. <line number="2">world</line>
  20. </content>
  21. </result>`)).toEqual([
  22. '[label]result[/label]',
  23. ' [label]path:[/label] /tmp/a.txt',
  24. ' [label]type:[/label] file',
  25. ' [label]content[/label]',
  26. ' [label]line (number="1"):[/label] hello',
  27. ' [label]line (number="2"):[/label] world',
  28. ])
  29. })
  30. it('renders root text, CDATA, empty elements, and multiline nested text', () => {
  31. expect(render(' <result>\nfirst\nsecond\n</result> ')).toEqual([
  32. '[label]result[/label]',
  33. ' first',
  34. ' second',
  35. ])
  36. expect(render('<result>\nfirst\nsecond\n</result>', 1, true)).toEqual([
  37. '[label]result[/label]',
  38. ' first',
  39. ' second',
  40. ])
  41. expect(render('<result><value><![CDATA[literal <xml>]]></value><empty /></result>')).toEqual([
  42. '[label]result[/label]',
  43. ' [label]value:[/label] literal <xml>',
  44. ' [label]empty[/label]',
  45. ])
  46. })
  47. it('previews each top-level child independently and expands all rows', () => {
  48. const xml = '<result><first>\na\nb\nc\nd\ne\nf\n</first><second>\ng\nh\ni\nj\nk\nl\n</second></result>'
  49. expect(render(xml, 3)).toEqual([
  50. '[label]result[/label]',
  51. ' [label]first[/label]',
  52. ' a',
  53. ' … +4 lines',
  54. ' f',
  55. ' [label]second[/label]',
  56. ' g',
  57. ' … +4 lines',
  58. ' l',
  59. ])
  60. expect(render(xml, 3, true)).toHaveLength(15)
  61. })
  62. it('bounds the collapsed child count and counts the hidden lines', () => {
  63. const xml = `<result>${Array.from({ length: 8 }, (_, index) => `<item>${index}</item>`).join('')}</result>`
  64. expect(render(xml, 3)).toEqual([
  65. '[label]result[/label]',
  66. ' [label]item:[/label] 0',
  67. ' [label]item:[/label] 1',
  68. ' … +5 lines',
  69. ' [label]item:[/label] 7',
  70. ])
  71. expect(render(xml, 3, true)).toHaveLength(9)
  72. })
  73. it('escapes control characters expanded from character references', () => {
  74. expect(render('<result attr="a&#155;b">tab&#9;csi&#155;</result>')).toEqual([
  75. '[label]result (attr="a\\\\x9bb")[/label]',
  76. ' tab\\x09csi\\x9b',
  77. ])
  78. expect(render('<result><value><![CDATA[del\u007f]]></value></result>')).toEqual([
  79. '[label]result[/label]',
  80. ' [label]value:[/label] del\\x7f',
  81. ])
  82. })
  83. it.each([
  84. '<result><path>missing close</result>',
  85. '<first /><second />',
  86. '<result /> <![CDATA[trailing]]>',
  87. 'prefix <result><path>/tmp/a</path></result>',
  88. '<result><path>/tmp/a</path></result> suffix',
  89. '<?xml version="1.0"?><result />',
  90. '<result><?target value?></result>',
  91. '<!DOCTYPE result><result />',
  92. '<result><!-- comment --></result>',
  93. '',
  94. ' \n ',
  95. ])('declines malformed or mixed text: %s', (source) => {
  96. expect(render(source)).toBeUndefined()
  97. })
  98. })