// @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('正文'); }); });