返回 html-video
spawn-utf8.test.ts
根目录 / packages / runtime / test / spawn-utf8.test.ts
1 import { test } from 'node:test';
2 import assert from 'node:assert/strict';
3 import { spawnAgent } from '../dist/index.js';
4 import type { AgentDef } from '../dist/index.js';
5
6 // Regression coverage for issue #9 mojibake (◆◆◆): a multi-byte UTF-8 char
7 // split across two stdout `data` chunks must not become U+FFFD. spawn.ts now
8 // decodes through StringDecoder, which buffers an incomplete trailing sequence.
9
10 // A child that prints CJK text ONE BYTE AT A TIME with a tick between bytes, so
11 // every 3-byte glyph is guaranteed to straddle multiple `data` events.
12 const BYTE_AT_A_TIME = `
13 const s = Buffer.from('设计引擎:每次使用都是一次进化。', 'utf8');
14 let i = 0;
15 (function next() {
16 if (i >= s.length) { process.exit(0); return; }
17 process.stdout.write(Buffer.from([s[i++]]));
18 setTimeout(next, 1);
19 })();
20 `;
21
22 function makeNodeDef(code: string): AgentDef {
23 return {
24 id: 'test-echo',
25 name: 'test-echo',
26 bin: process.execPath, // node
27 versionArgs: ['--version'],
28 buildArgs: () => ['-e', code],
29 streamFormat: 'plain',
30 };
31 }
32
33 test('CJK split across stdout chunks is not corrupted', async () => {
34 let collected = '';
35 const handle = spawnAgent({
36 def: makeNodeDef(BYTE_AT_A_TIME),
37 prompt: '',
38 context: { cwd: process.cwd() },
39 onEvent: (ev) => {
40 if (ev.type === 'text') collected += ev.chunk;
41 },
42 });
43 const { exitCode } = await handle.done;
44 assert.equal(exitCode, 0);
45 assert.equal(collected, '设计引擎:每次使用都是一次进化。');
46 assert.ok(!collected.includes('�'), 'must contain no U+FFFD replacement chars');
47 });
48
48 lines TYPESCRIPT