Files
upage-git/app/.client/runtime/message-parser.spec.ts

213 lines
7.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { type ActionCallback, type ArtifactCallback, StreamingMessageParser } 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 upage artifacts (%#)', (input, expected) => {
runTest(input, expected);
});
});
describe('invalid or incomplete artifacts', () => {
it.each<[string | string[], ExpectedResult | string]>([
['Foo bar <u', 'Foo bar '],
['Foo bar <uP', 'Foo bar '],
['Foo bar <uPa', 'Foo bar '],
['Foo bar <uPag', 'Foo bar '],
['Foo bar <uPage', 'Foo bar '],
['Foo bar <uPageA', 'Foo bar '],
['Foo bar <uPageArtifacs></uPageArtifact>', 'Foo bar <uPageArtifacs></uPageArtifact>'],
['Before <PageArtfiact>foo</uPageArtifact> After', 'Before <PageArtfiact>foo</uPageArtifact> After'],
['Before <uPageArtifactt>foo</uPageArtifact> After', 'Before <uPageArtifactt>foo</uPageArtifact> After'],
])('should correctly parse chunks and strip out upage artifacts (%#)', (input, expected) => {
runTest(input, expected);
});
});
describe('valid artifacts without actions', () => {
it.each<[string | string[], ExpectedResult | string]>([
[
'Some text before <uPageArtifact id="artifact_1" name="index" title="Some title">foo bar</uPageArtifact> Some more text',
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
[
'Some text before <uPageArti',
'fact',
' name="index" title="Some title" id="artifact_1">foo</uPageArtifact> Some more text',
],
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
[
'Some text before <uPageArti',
'fac',
't title="Some title" id="artifact_1" name="index"',
' ',
'>',
'foo</uPageArtifact> Some more text',
],
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
[
'Some text before <uPageArti',
'fact',
' title="Some title" id="artifact_1" name="index"',
' >fo',
'o</uPageArtifact> Some more text',
],
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
[
'Some text before <uPageArti',
'fact tit',
'le="Some ',
'title" id="artifact_1" name="index">fo',
'o',
'<',
'/uPageArtifact> Some more text',
],
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
[
'Some text before <uPageArti',
'fact title="Some title" id="artif',
' name="index"',
'act_1">fo',
'o<',
'/uPageArtifact> Some more text',
],
{
output: 'Some text before Some more text',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
[
'Before <uPageArtifact title="Some title" id="artifact_1" name="index">foo</uPageArtifact> After',
{
output: 'Before After',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 0, onActionClose: 0 },
},
],
])('should correctly parse chunks and strip out uPage artifacts (%#)', (input, expected) => {
runTest(input, expected);
});
});
describe('valid artifacts with actions', () => {
it.each<[string | string[], ExpectedResult | string]>([
[
'Before <uPageArtifact title="Some title" id="artifact_1" name="index"><uPageAction id="action_1" pageName="index" action="add" domId="page-index" rootDomId="section-1">npm install</uPageAction></uPageArtifact> After',
{
output: 'Before After',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 1, onActionClose: 1 },
},
],
[
'Before <uPageArtifact title="Some title" id="artifact_1" name="index"><uPageAction id="action_1" pageName="index" action="add" domId="page-index" rootDomId="section-1">npm install</uPageAction><uPageAction id="action_2" pageName="index" action="update" domId="section-1" rootDomId="section-1">some content</uPageAction></uPageArtifact> After',
{
output: 'Before After',
callbacks: { onArtifactOpen: 1, onArtifactClose: 1, onActionOpen: 2, onActionClose: 2 },
},
],
])('should correctly parse chunks and strip out uPage 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);
}