import { useEffect } from "react"; import { act, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { MarkdownText } from "@/components/MarkdownText"; const rendererSpy = vi.hoisted(() => vi.fn()); const rendererMountSpy = vi.hoisted(() => vi.fn()); const rendererControl = vi.hoisted(() => ({ failStreaming: false })); vi.mock("@/components/MarkdownTextRenderer", () => ({ default: function MockMarkdownTextRenderer({ children, highlightCode, streaming, }: { children: string; highlightCode?: boolean; streaming?: boolean; }) { useEffect(() => { rendererMountSpy(); }, []); if (streaming && rendererControl.failStreaming) { throw new Error("incomplete streaming markdown"); } rendererSpy({ children, highlightCode }); return (
{children}
); }, })); describe("MarkdownText", () => { it("recovers markdown rendering when a failed streaming response completes", async () => { rendererControl.failStreaming = true; const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}); const source = "## Final answer\n\nThis is **important**."; try { const { container, rerender } = render( {source}, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(container.querySelector(".streaming-text-fallback")?.textContent).toBe(source); rendererControl.failStreaming = false; rerender({source}); expect(screen.getByTestId("markdown-renderer").textContent).toBe(source); } finally { rendererControl.failStreaming = false; consoleError.mockRestore(); } }); it("forwards every provider update without an extra UI timer", async () => { rendererSpy.mockClear(); const { rerender } = render( hello, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("markdown-renderer")).toHaveTextContent("hello"); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-highlight-code", "false", ); rerender(hello world); expect(screen.getByTestId("markdown-renderer")).toHaveTextContent("hello world"); rerender(hello world!!!); expect(screen.getByTestId("markdown-renderer")).toHaveTextContent("hello world!!!"); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-highlight-code", "true", ); }); it("keeps a healthy renderer mounted when streaming completes", async () => { rendererMountSpy.mockClear(); const { rerender } = render( hello, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); rerender(hello world); expect(rendererMountSpy).toHaveBeenCalledTimes(1); }); it("can complete without replacing the streaming renderer layout", async () => { const source = "A layout-stable answer"; const { rerender } = render( {source}, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-streaming-layout", "true", ); rerender({source}); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-streaming-layout", "true", ); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-highlight-code", "true", ); }); it("defers syntax highlighting until the final render", async () => { rendererSpy.mockClear(); const largeCode = `\`\`\`ts\n${"const value = 1;\n".repeat(1_100)}\`\`\``; const { rerender } = render( {largeCode}, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-highlight-code", "false", ); rerender({largeCode}); expect(screen.getByTestId("markdown-renderer")).toHaveAttribute( "data-highlight-code", "true", ); }); });