import { describe, expect, it, vi } from 'vitest'; import { StreamingMessageParser, type ActionCallback, type ArtifactCallback } from './message-parser'; interface ExpectedResult { output: string; callbacks?: { onArtifactOpen?: number; onArtifactClose?: number; onActionOpen?: number; onActionClose?: number; }; } describe('StreamingMessageParser', () => { it('should pass through normal text', () => { const parser = new StreamingMessageParser(); expect(parser.parse('test_id', 'Hello, world!')).toBe('Hello, world!'); }); it('should allow normal HTML tags', () => { const parser = new StreamingMessageParser(); expect(parser.parse('test_id', 'Hello <strong>world</strong>!')).toBe('Hello <strong>world</strong>!'); }); describe('no artifacts', () => { it.each<[string | string[], ExpectedResult | string]>([ ['Foo bar', 'Foo bar'], ['Foo bar <', 'Foo bar '], ['Foo bar <p', 'Foo bar <p'], [['Foo bar <', 's', 'p', 'an>some text</span>'], 'Foo bar <span>some text</span>'], ])('should correctly parse chunks and strip out bolt artifacts (%#)', (input, expected) => { runTest(input, expected); }); }); describe('invalid or incomplete artifacts', () => { it.each<[string | string[], ExpectedResult | string]>([ ['Foo bar <b', 'Foo bar '], ['Foo bar <ba', 'Foo bar <ba'], ['Foo bar <bol', 'Foo bar '], ['Foo bar <bolt', 'Foo bar '], ['Foo bar <bolta', 'Foo bar <bolta'], ['Foo bar <boltA', 'Foo bar '], ['Foo bar <boltArtifacs></boltArtifact>', 'Foo bar <boltArtifacs></boltArtifact>'], ['Before <oltArtfiact>foo</boltArtifact> After', 'Before <oltArtfiact>foo</boltArtifact> After'], ['Before <boltArtifactt>foo</boltArtifact> After', 'Before <boltArtifactt>foo</boltArtifact> After'], ])('should correctly parse chunks and strip out bolt artifacts (%#)', (input, expected) => { runTest(input, expected); }); }); describe('valid artifacts without actions', () => { it.each<[string | string[], ExpectedResult | string]>([ [ 'Some text before <boltArtifact title="Some title" id="artifact_1">foo bar</boltArtifact> Some more text', { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ [ 'Some text before <boltArti', 'fact', ' title="Some title" id="artifact_1" type="bundled" >foo</boltArtifact> Some more text', ], { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ [ 'Some text before <boltArti', 'fac', 't title="Some title" id="artifact_1"', ' ', '>', 'foo</boltArtifact> Some more text', ], { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ [ 'Some text before <boltArti', 'fact', ' title="Some title" id="artifact_1"', ' >fo', 'o</boltArtifact> Some more text', ], { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ [ 'Some text before <boltArti', 'fact tit', 'le="Some ', 'title" id="artifact_1">fo', 'o', '<', '/boltArtifact> Some more text', ], { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ [ 'Some text before <boltArti', 'fact title="Some title" id="artif', 'act_1">fo', 'o<', '/boltArtifact> Some more text', ], { output: 'Some text before Some more text', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], [ 'Before <boltArtifact title="Some title" id="artifact_1">foo</boltArtifact> After', { output: 'Before After', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 }, }, ], ])('should correctly parse chunks and strip out bolt artifacts (%#)', (input, expected) => { runTest(input, expected); }); }); describe('valid artifacts with actions', () => { it.each<[string | string[], ExpectedResult | string]>([ [ 'Before <boltArtifact title="Some title" id="artifact_1"><boltAction type="shell">npm install</boltAction></boltArtifact> After', { output: 'Before After', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 1, onActionClose: 1 }, }, ], [ 'Before <boltArtifact title="Some title" id="artifact_1"><boltAction type="shell">npm install</boltAction><boltAction type="file" filePath="index.js">some content</boltAction></boltArtifact> After', { output: 'Before After', callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 2, onActionClose: 2 }, }, ], ])('should correctly parse chunks and strip out bolt artifacts (%#)', (input, expected) => { runTest(input, expected); }); }); }); function runTest(input: string | string[], outputOrExpectedResult: string | ExpectedResult) { let expected: ExpectedResult; if (typeof outputOrExpectedResult === 'string') { expected = { output: outputOrExpectedResult }; } else { expected = outputOrExpectedResult; } const callbacks = { onArtifactOpen: vi.fn<ArtifactCallback>((data) => { expect(data).toMatchSnapshot('onArtifactOpen'); }), onArtifactClose: vi.fn<ArtifactCallback>((data) => { expect(data).toMatchSnapshot('onArtifactClose'); }), onActionOpen: vi.fn<ActionCallback>((data) => { expect(data).toMatchSnapshot('onActionOpen'); }), onActionClose: vi.fn<ActionCallback>((data) => { expect(data).toMatchSnapshot('onActionClose'); }), }; const parser = new StreamingMessageParser({ artifactElement: () => '', callbacks, }); let message = ''; let result = ''; const chunks = Array.isArray(input) ? input : input.split(''); for (const chunk of chunks) { message += chunk; result += parser.parse('message_1', message); } for (const name in expected.callbacks) { const callbackName = name; expect(callbacks[callbackName as keyof typeof callbacks]).toHaveBeenCalledTimes( expected.callbacks[callbackName as keyof typeof expected.callbacks] ?? 0, ); } expect(result).toEqual(expected.output); }