Skip to content

Commit 11b7417

Browse files
authored
test(chat,cockpit): fence streaming regression guards; retire stale parser-workaround comment (#946)
* test(chat): guard split-fence recovery against a partial-markdown downgrade * test(cockpit): stream a split code fence through the c-messages harness at chunkSize 3
1 parent 8687258 commit 11b7417

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

cockpit/chat/messages/angular/e2e/c-messages.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,13 @@ test('c-messages: chat-message-list renders both turns', async ({ page }) => {
2121
// list renders one bubble per message. Regression coverage for that fix.
2222
await expect(page.locator('chat-message-list chat-message')).toHaveCount(2);
2323
});
24+
25+
test('c-messages: a code fence streamed in 3-char chunks renders as a code block', async ({ page }) => {
26+
const bubble = await submitAndWaitForResponse(page, 'Stream a TypeScript code fence');
27+
28+
// Final-state invariant driven through REAL small-chunk streaming: the
29+
// fence opener arrives split mid-token and must still commit as a block.
30+
await expect(bubble.locator('pre code')).toHaveCount(1);
31+
await expect(bubble.locator('pre code')).toContainText('const answer = 42;');
32+
await expect(bubble).not.toContainText('```');
33+
});

cockpit/chat/messages/angular/e2e/fixtures/c-messages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
"response": {
66
"content": "Hi! I'm the chat-messages capability demo. I show how ChatMessageListComponent, ChatInputComponent, and ChatTypingIndicatorComponent render together. Try sending a few messages to see the bubbles and typing indicator in action."
77
}
8+
},
9+
{
10+
"match": { "userMessage": "Stream a TypeScript code fence" },
11+
"response": {
12+
"content": "Here is the snippet:\n\n```typescript\nconst answer = 42;\n```\n\nThe constant is available for later use."
13+
},
14+
"chunkSize": 3,
15+
"latency": 25
816
}
917
]
1018
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
//
3+
// Consumer-side guarantee: with the partial-markdown version chat depends on,
4+
// a triple-backtick fence whose opener is split across streamed chunks still
5+
// materializes as a code block — never as a paragraph with inline code.
6+
// Fixed upstream in 0.5.6 (opener-marker tracking); this spec guards against
7+
// a dependency downgrade reintroducing it. The e2e harnesses' large default
8+
// chunkSize is a determinism choice, not a workaround for this — see
9+
// libs/e2e-harness/src/aimock-runner.ts.
10+
import { describe, it, expect } from 'vitest';
11+
import { createPartialMarkdownParser, materialize } from '@cacheplane/partial-markdown';
12+
13+
function finalTypes(chunks: string[]): string[] {
14+
const p = createPartialMarkdownParser();
15+
for (const chunk of chunks) p.push(chunk);
16+
p.finish();
17+
const doc = materialize(p.root) as { children?: Array<{ type: string }> } | null;
18+
return (doc?.children ?? []).map((c) => c.type);
19+
}
20+
21+
describe('libs/chat consumes streamed code fences', () => {
22+
it('recovers an opener split one backtick at a time', () => {
23+
expect(finalTypes(['`', '`', '`ts\n', 'const x = 1;\n', '```\n']))
24+
.toEqual(['code-block']);
25+
});
26+
27+
it('recovers a closer split one backtick at a time', () => {
28+
expect(finalTypes(['```ts\nconst x = 1;\n', '`', '`', '`\n']))
29+
.toEqual(['code-block']);
30+
});
31+
32+
it('survives arbitrary small chunkings of fenced content after prose', () => {
33+
const text = 'Here is the snippet:\n\n```typescript\nconst answer = 42;\n```\n\nDone.\n';
34+
for (let chunkSize = 1; chunkSize <= 7; chunkSize++) {
35+
const chunks: string[] = [];
36+
for (let i = 0; i < text.length; i += chunkSize) chunks.push(text.slice(i, i + chunkSize));
37+
const types = finalTypes(chunks);
38+
expect(types, `chunkSize ${chunkSize}`).toEqual(['paragraph', 'code-block', 'paragraph']);
39+
}
40+
});
41+
42+
it('keeps genuine inline code inline', () => {
43+
expect(finalTypes(['Use `npm', ' i` first.\n'])).toEqual(['paragraph']);
44+
});
45+
});

0 commit comments

Comments
 (0)