import { describe, it, expect } from 'vitest'; import { extractXMLFromString, wrapXmlBlocksInContent } from './xmlUtil'; import { longTextWithAddNotes, longTextWithReadMusic, multipleXmlBlocks, nestedXmlContent, xmlWithAttributes, malformedXml, noXmlContent, emptyAndWhitespaceXml } from '../test/fixtures/xml-samples'; describe('xmlUtil', () => { describe('extractXMLFromString', () => { it('should extract simple XML blocks', () => { const input = `Here is some text with content and more text.`; const result = extractXMLFromString(input); expect(result).toHaveLength(1); expect(result[0]).toBe('content'); }); it('should extract multiple XML blocks', () => { const input = `content1 some text content2`; const result = extractXMLFromString(input); expect(result).toHaveLength(2); expect(result[0]).toBe('content1'); expect(result[1]).toBe('content2'); }); it('should extract XML blocks with nested elements', () => { const input = `nested content`; const result = extractXMLFromString(input); expect(result).toHaveLength(1); expect(result[0]).toBe('nested content'); }); it('should extract XML blocks with attributes', () => { const input = `content`; const result = extractXMLFromString(input); expect(result).toHaveLength(1); expect(result[0]).toBe('content'); }); it('should handle multiline XML blocks', () => { const input = ` content1 content2 `; const result = extractXMLFromString(input); expect(result).toHaveLength(1); expect(result[0]).toContain(''); expect(result[0]).toContain('content1'); expect(result[0]).toContain('content2'); expect(result[0]).toContain(''); }); it('should extract XML from complex nested content fixture', () => { const result = extractXMLFromString(nestedXmlContent); expect(result).toHaveLength(1); expect(result[0]).toContain(''); expect(result[0]).toContain(''); expect(result[0]).toContain(''); expect(result[0]).toContain('Test Content'); expect(result[0]).toContain(''); }); it('should extract multiple XML blocks from fixture', () => { const result = extractXMLFromString(multipleXmlBlocks); expect(result).toHaveLength(3); expect(result[0]).toContain(''); expect(result[0]).toContain(''); expect(result[1]).toContain(''); expect(result[1]).toContain(''); expect(result[2]).toContain(''); expect(result[2]).toContain(''); }); it('should extract XML with attributes from fixture', () => { const result = extractXMLFromString(xmlWithAttributes); expect(result).toHaveLength(1); expect(result[0]).toContain('region_id="main"'); expect(result[0]).toContain('track="melody"'); expect(result[0]).toContain('id="1"'); expect(result[0]).toContain('velocity="127"'); }); it('should handle the long text with add_notes fixture (Case 1)', () => { const result = extractXMLFromString(longTextWithAddNotes); expect(result).toHaveLength(2); // Contains thinking tag and add_notes block // Find the add_notes block const addNotesBlock = result.find(block => block.includes('')); expect(addNotesBlock).toBeDefined(); expect(addNotesBlock).toContain(''); expect(addNotesBlock).toContain(''); expect(addNotesBlock).toContain('C4'); expect(addNotesBlock).toContain('0'); expect(addNotesBlock).toContain('4'); expect(addNotesBlock).toContain(''); // Should contain all 12 notes const noteMatches = addNotesBlock!.match(//g); expect(noteMatches).toHaveLength(12); }); it('should handle the long text with read_music fixture (Case 2)', () => { const result = extractXMLFromString(longTextWithReadMusic); expect(result).toHaveLength(2); // First XML block (inside thinking tag) expect(result[0]).toContain(''); expect(result[0]).toContain('0'); expect(result[0]).toContain('32'); expect(result[0]).toContain(''); // Second XML block (at the end) expect(result[1]).toContain(''); expect(result[1]).toContain('0'); expect(result[1]).toContain('32'); expect(result[1]).toContain(''); }); it('should handle malformed XML gracefully', () => { const result = extractXMLFromString(malformedXml); // Should extract valid XML blocks (ignores malformed ones) expect(result.length).toBeGreaterThanOrEqual(1); // Find the definitely valid block const validBlock = result.find(block => block.includes('')); expect(validBlock).toBeDefined(); expect(validBlock).toContain('This is valid'); }); it('should return empty array for content with no XML', () => { const result = extractXMLFromString(noXmlContent); expect(result).toHaveLength(0); expect(result).toEqual([]); }); it('should handle empty and whitespace XML', () => { const result = extractXMLFromString(emptyAndWhitespaceXml); expect(result).toHaveLength(3); expect(result[0]).toBe(''); expect(result[1]).toContain(''); expect(result[1]).toContain(''); expect(result[2]).toContain(''); expect(result[2]).toContain('Some text with spaces'); expect(result[2]).toContain(''); }); it('should handle XML with underscores and hyphens in tag names', () => { const input = `content and content`; const result = extractXMLFromString(input); expect(result).toHaveLength(2); expect(result[0]).toBe('content'); expect(result[1]).toBe('content'); }); it('should handle self-closing tags (not currently supported)', () => { const input = ` and content`; const result = extractXMLFromString(input); // Current implementation doesn't support self-closing tags expect(result).toHaveLength(1); expect(result[0]).toBe('content'); }); it('should trim whitespace around extracted XML', () => { const input = ` content `; const result = extractXMLFromString(input); expect(result).toHaveLength(1); expect(result[0]).toBe('content'); }); }); describe('wrapXmlBlocksInContent', () => { it('should wrap single XML block in fenced code block', () => { const input = `Here is content in text.`; const result = wrapXmlBlocksInContent(input); expect(result).toBe('Here is ```xml\ncontent\n``` in text.'); }); it('should wrap multiple XML blocks', () => { const input = `content1 text content2`; const result = wrapXmlBlocksInContent(input); expect(result).toContain('```xml\ncontent1\n```'); expect(result).toContain('```xml\ncontent2\n```'); }); it('should return original content when no XML blocks present', () => { const input = noXmlContent; const result = wrapXmlBlocksInContent(input); expect(result).toBe(input); }); it('should handle empty input', () => { expect(wrapXmlBlocksInContent('')).toBe(''); expect(wrapXmlBlocksInContent(null as unknown as string)).toBeNull(); expect(wrapXmlBlocksInContent(undefined as unknown as string)).toBeUndefined(); }); it('should wrap XML blocks from multipleXmlBlocks fixture', () => { const result = wrapXmlBlocksInContent(multipleXmlBlocks); expect(result).toContain('```xml\n'); expect(result).toContain('\n```'); expect(result).toContain('```xml\n'); expect(result).toContain('\n```'); expect(result).toContain('```xml\n'); expect(result).toContain('\n```'); // Should preserve the surrounding text expect(result).toContain('Here\'s how to add multiple musical elements:'); expect(result).toContain('First, let\'s add some notes:'); expect(result).toContain('Then we can read the current music:'); }); it('should wrap the long add_notes XML block (Case 1)', () => { const result = wrapXmlBlocksInContent(longTextWithAddNotes); expect(result).toContain('```xml\n'); expect(result).toContain('\n```'); // Should preserve the thinking content and other text expect(result).toContain(''); expect(result).toContain('Perfect! I can see this is a beautiful'); expect(result).toContain('Let me start by adding the harmony'); }); it('should wrap the long read_music XML blocks (Case 2)', () => { const result = wrapXmlBlocksInContent(longTextWithReadMusic); // Should contain two wrapped XML blocks const fencedBlocks = result.match(/```xml\n[\s\S]*?<\/read_music>\n```/g); expect(fencedBlocks).toHaveLength(2); // Should preserve the thinking content and other text expect(result).toContain(''); expect(result).toContain('I\'ll help you create a pad harmony track'); expect(result).toContain('Let me first read the existing music'); }); it('should handle nested XML correctly', () => { const result = wrapXmlBlocksInContent(nestedXmlContent); expect(result).toContain('```xml\n'); expect(result).toContain(''); expect(result).toContain(''); expect(result).toContain('Test Content'); expect(result).toContain('\n```'); }); it('should preserve XML block integrity when wrapping', () => { const input = `Text before\n\n value\n content\n\nText after`; const result = wrapXmlBlocksInContent(input); expect(result).toBe(`Text before\n\`\`\`xml\n\n value\n content\n\n\`\`\`\nText after`); }); it('should handle duplicate XML blocks correctly', () => { const input = `content and then content again`; const result = wrapXmlBlocksInContent(input); // Both instances should be wrapped const wrappedBlocks = result.match(/```xml\ncontent<\/same>\n```/g); expect(wrappedBlocks).toHaveLength(2); }); }); describe('edge cases and error handling', () => { it('should handle very large XML blocks', () => { const largeContent = 'x'.repeat(10000); const input = `${largeContent}`; const extracted = extractXMLFromString(input); expect(extracted).toHaveLength(1); expect(extracted[0]).toContain(largeContent); const wrapped = wrapXmlBlocksInContent(input); expect(wrapped).toContain('```xml\n'); expect(wrapped).toContain('\n```'); }); it('should handle XML with special characters', () => { const input = `Content with < > & " '`; const extracted = extractXMLFromString(input); expect(extracted).toHaveLength(1); expect(extracted[0]).toContain('< > & " ''); const wrapped = wrapXmlBlocksInContent(input); expect(wrapped).toContain('```xml\nContent with < > & " '\n```'); }); it('should handle XML with CDATA sections', () => { const input = ` chars]]>`; const extracted = extractXMLFromString(input); expect(extracted).toHaveLength(1); expect(extracted[0]).toContain(''); }); it('should handle mixed content with partial XML-like text', () => { const input = `This < is not XML and neither > is this content is valid`; const extracted = extractXMLFromString(input); expect(extracted).toHaveLength(1); expect(extracted[0]).toBe('content'); }); }); });