From fd474724cb4d8d66bf5377b6a34ee65b0ae09425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Fri, 4 Sep 2026 13:37:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B5=81=E5=BC=8F=20Markdown?= =?UTF-8?q?=20=E9=94=99=E8=AF=AF=E8=BE=B9=E7=95=8C=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 累计文本变化时清除错误边界状态并重新尝试 Markdown 渲染。 新增首次渲染失败后随文本更新恢复的回归测试。 --- .../components/ChatMarkdownMessage/index.tsx | 11 +++++- .../tests/ChatMarkdownMessage.test.tsx | 39 ++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx index 3578d5801..ab7df6394 100644 --- a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx @@ -18,7 +18,7 @@ type MarkdownErrorBoundaryState = { hasError: boolean; }; -class MarkdownErrorBoundary extends Component< +export class MarkdownErrorBoundary extends Component< MarkdownErrorBoundaryProps, MarkdownErrorBoundaryState > { @@ -32,6 +32,15 @@ class MarkdownErrorBoundary extends Component< // Keep the original message visible without logging its potentially sensitive content. } + componentDidUpdate(prevProps: MarkdownErrorBoundaryProps) { + if ( + this.state.hasError && + prevProps.fallbackText !== this.props.fallbackText + ) { + this.setState({ hasError: false }); + } + } + render() { if (this.state.hasError) { return ( diff --git a/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx b/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx index 82aec9c8e..149b4d25c 100644 --- a/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx +++ b/apps/ai-game-creator-shell/tests/ChatMarkdownMessage.test.tsx @@ -1,9 +1,19 @@ // @vitest-environment jsdom import { render } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; -import { ChatMarkdownMessage } from '../src/components/ChatMarkdownMessage'; +import { + ChatMarkdownMessage, + MarkdownErrorBoundary, +} from '../src/components/ChatMarkdownMessage'; + +function FailingChild({ shouldThrow }: { shouldThrow: boolean }) { + if (shouldThrow) { + throw new Error('transient render failure'); + } + return markdown recovered; +} describe('ChatMarkdownMessage', () => { it('渲染 assistant 的 GFM 内容与代码块', () => { @@ -95,6 +105,31 @@ describe('ChatMarkdownMessage', () => { expect(code?.className).not.toContain('rounded'); }); + it('累计文本变化后可从 Markdown 错误回退中恢复', () => { + const consoleError = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + try { + const { container, rerender } = render( + + + , + ); + + expect(container.textContent).toBe('partial'); + + rerender( + + + , + ); + + expect(container.textContent).toBe('markdown recovered'); + } finally { + consoleError.mockRestore(); + } + }); + it('保留流式 assistant 文本并标记 streaming 状态', () => { const { container } = render(