import { describe, it, expect } from 'vitest';
import { stripDcpTags, makeDcpStreamStripper } from '../dcp-strip.js';
// Feed chunks through a fresh stripper and return the fully reassembled output
// (everything emitted during streaming + the final flush) — i.e. what the
// dispatcher would accumulate into the persisted message content.
function run(chunks: string[]): string {
const s = makeDcpStreamStripper();
let out = '';
for (const c of chunks) out += s.push(c);
out += s.flush();
return out;
}
describe('stripDcpTags (one-shot)', () => {
it('removes a complete tag', () => {
expect(stripDcpTags('Yes — "Test".\n\nm0019')).toBe(
'Yes — "Test".\n\n',
);
});
it('leaves text without a tag untouched', () => {
expect(stripDcpTags('no tag here')).toBe('no tag here');
});
});
describe('per-chunk strip is INSUFFICIENT (documents the bug)', () => {
it('a tag split across chunks survives a naive per-chunk .replace()', () => {
const chunks = ['Yes.\n\nm0019'];
const naive = chunks.map(stripDcpTags).join('');
// The reassembled content still contains the tag — this is the screenshot bug.
expect(naive).toContain('m0019');
});
});
describe('makeDcpStreamStripper (cross-chunk fix)', () => {
it('strips a tag split across chunks (the real opencode case)', () => {
expect(run(['Yes.\n\nm0019'])).toBe('Yes.\n\n');
});
it('strips a tag split at EVERY character boundary', () => {
const full = 'Answer.m0019';
expect(run([...full])).toBe('Answer.');
});
it('strips a tag delivered whole in one chunk', () => {
expect(run(['Answer.m0019'])).toBe('Answer.');
});
it('passes through text with no tag', () => {
expect(run(['hello ', 'world'])).toBe('hello world');
});
it('does NOT swallow legitimate < content (code/HTML/generics)', () => {
expect(run(['use ', '
', ' and ', 'Array<', 'string>'])).toBe('use
and Array');
});
it('handles a lone < that is not a dcp tag, split across chunks', () => {
expect(run(['a <', 'b c'])).toBe('a {
expect(run(['before ', '', 'm1', '', ' after'])).toBe(
'before after',
);
});
it('flushes a truncated/never-closed partial tag without leaking it as a complete tag', () => {
// If the stream ends mid-tag, flush strips complete tags; an incomplete
// remnant is returned as-is (no complete tag ever existed to render).
const out = run(['done.m00']);
expect(out).not.toContain('');
});
});