Files
makelore/tests/unit/teacher-reply-stream.test.ts

39 lines
2.0 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it } from 'vitest';
import { previewTeacherReply } from '../../shared/teacher-reply';
describe('teacher streaming prose', () => {
it('decodes text before the envelope or string closes, withholding incomplete escapes', () => {
const reply = '先看 "代码"。\n路径 C:\\game\n🍎';
const raw = JSON.stringify({ reply, quickReplies: ['下一步'], tool: { reply: 'never-visible' } });
const previews = Array.from({ length: raw.length + 1 }, (_, i) => previewTeacherReply(raw.slice(0, i)));
for (const preview of previews) {
expect(reply.startsWith(preview)).toBe(true);
expect(preview).not.toContain('quickReplies');
}
expect(previews[raw.indexOf('代码') + 2]).toBe('先看 "代码');
expect(previews.at(-1)).toBe(reply);
expect(previewTeacherReply('{"reply":"你好\\u4')).toBe('你好');
expect(previewTeacherReply('{"reply":"你好\\u4e16')).toBe('你好世');
});
it.each(['```json\n', '```JSON\r\n', '~~~json\n', '```makelore-teacher-reply\n', '```\n'])(
'streams fenced replies without leaking the protocol (%s)', fence => {
const raw = fence + '{"quickReplies":[],"reply":"逐步回答';
for (let i = 0; i <= raw.length; i++) {
const preview = previewTeacherReply(raw.slice(0, i));
expect('逐步回答'.startsWith(preview)).toBe(true);
}
expect(previewTeacherReply(raw)).toBe('逐步回答');
},
);
it('accepts plain text and completed ordinary JSON while excluding nested retired payloads', () => {
expect(previewTeacherReply('## 标题\n\n逐步说明')).toBe('## 标题\n\n逐步说明');
expect(previewTeacherReply('{"count":1}')).toBe('{"count":1}');
expect(previewTeacherReply('{"tool":{"reply":"不要显示"},"reply":"正文')).toBe('正文');
expect(previewTeacherReply('{"intro":"旧版正文","questions":[')).toBe('旧版正文');
expect(previewTeacherReply('{"reply":"正文","quickReplies":["隐藏')).toBe('正文');
});
});