修复流式 Markdown 错误边界恢复
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled

累计文本变化时清除错误边界状态并重新尝试 Markdown 渲染。

新增首次渲染失败后随文本更新恢复的回归测试。
This commit is contained in:
2026-09-04 13:37:14 +08:00
parent 8c2a4fea64
commit fd474724cb
2 changed files with 47 additions and 3 deletions
@@ -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 (
@@ -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 <span>markdown recovered</span>;
}
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(
<MarkdownErrorBoundary fallbackText="partial">
<FailingChild shouldThrow />
</MarkdownErrorBoundary>,
);
expect(container.textContent).toBe('partial');
rerender(
<MarkdownErrorBoundary fallbackText="complete">
<FailingChild shouldThrow={false} />
</MarkdownErrorBoundary>,
);
expect(container.textContent).toBe('markdown recovered');
} finally {
consoleError.mockRestore();
}
});
it('保留流式 assistant 文本并标记 streaming 状态', () => {
const { container } = render(
<ChatMarkdownMessage