1- import { describe , expect , it , vi } from 'vitest'
2- import { render , waitFor } from '@testing-library/react'
1+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
2+ import { cleanup , render , waitFor } from '@testing-library/react'
33
44const mermaidMocks = vi . hoisted ( ( ) => ( {
55 initializeMock : vi . fn ( ) ,
6- renderMock : vi . fn ( ) . mockResolvedValue ( {
7- svg : '<svg data-testid="mock-mermaid"></svg>'
8- } )
6+ parseMock : vi . fn ( ) ,
7+ renderMock : vi . fn ( ) ,
8+ setParseErrorHandlerMock : vi . fn ( ) ,
99} ) )
1010
1111vi . mock ( 'mermaid' , ( ) => ( {
1212 default : {
1313 initialize : mermaidMocks . initializeMock ,
14+ parse : mermaidMocks . parseMock ,
1415 render : mermaidMocks . renderMock ,
16+ setParseErrorHandler : mermaidMocks . setParseErrorHandlerMock ,
1517 }
1618} ) )
1719
1820import { MermaidDiagram } from '@/components/assistant-ui/mermaid-diagram'
1921import { MARKDOWN_COMPONENTS_BY_LANGUAGE } from '@/components/assistant-ui/markdown-text'
2022
23+ function renderMermaid ( code : string ) {
24+ return render (
25+ < MermaidDiagram
26+ code = { code }
27+ language = "mermaid"
28+ components = { {
29+ Pre : ( props ) => < pre { ...props } /> ,
30+ Code : ( props ) => < code { ...props } /> ,
31+ } }
32+ />
33+ )
34+ }
35+
2136describe ( 'MermaidDiagram' , ( ) => {
37+ beforeEach ( ( ) => {
38+ mermaidMocks . initializeMock . mockClear ( )
39+ mermaidMocks . setParseErrorHandlerMock . mockClear ( )
40+ mermaidMocks . parseMock . mockReset ( )
41+ mermaidMocks . parseMock . mockResolvedValue ( { diagramType : 'flowchart-v2' } )
42+ mermaidMocks . renderMock . mockReset ( )
43+ mermaidMocks . renderMock . mockResolvedValue ( {
44+ svg : '<svg data-testid="mock-mermaid"></svg>'
45+ } )
46+ } )
47+
48+ afterEach ( ( ) => {
49+ cleanup ( )
50+ document . documentElement . removeAttribute ( 'data-theme' )
51+ } )
52+
2253 it ( 'is wired into the shared markdown language overrides and renders svg output' , async ( ) => {
23- render (
24- < MermaidDiagram
25- code = { 'graph TD\nA --> B' }
26- language = "mermaid"
27- components = { {
28- Pre : ( props ) => < pre { ...props } /> ,
29- Code : ( props ) => < code { ...props } /> ,
30- } }
31- />
32- )
54+ renderMermaid ( 'graph TD\nA --> B' )
3355
3456 await waitFor ( ( ) => {
3557 const diagram = document . querySelector ( '[data-mermaid-diagram][data-rendered="true"]' )
@@ -39,9 +61,47 @@ describe('MermaidDiagram', () => {
3961
4062 expect ( mermaidMocks . initializeMock ) . toHaveBeenCalled ( )
4163 expect ( mermaidMocks . initializeMock ) . toHaveBeenCalledWith ( expect . objectContaining ( {
42- securityLevel : 'strict'
64+ securityLevel : 'strict' ,
65+ suppressErrorRendering : true ,
4366 } ) )
67+ expect ( mermaidMocks . parseMock ) . toHaveBeenCalledWith ( 'graph TD\nA --> B' , { suppressErrors : true } )
4468 expect ( mermaidMocks . renderMock ) . toHaveBeenCalledWith ( expect . stringContaining ( 'mermaid-' ) , 'graph TD\nA --> B' )
4569 expect ( MARKDOWN_COMPONENTS_BY_LANGUAGE . mermaid . SyntaxHighlighter ) . toBe ( MermaidDiagram )
4670 } )
71+
72+ it ( 'falls back to source and suppresses Mermaid parse-error side effects for invalid syntax' , async ( ) => {
73+ document . documentElement . dataset . theme = 'dark'
74+ mermaidMocks . parseMock . mockResolvedValueOnce ( false )
75+
76+ renderMermaid ( 'graph TD\nA --' )
77+
78+ await waitFor ( ( ) => {
79+ const fallback = document . querySelector ( '.aui-mermaid-fallback' )
80+ expect ( fallback ) . toBeTruthy ( )
81+ expect ( fallback ?. textContent ) . toBe ( 'graph TD\nA --' )
82+ } )
83+
84+ expect ( mermaidMocks . parseMock ) . toHaveBeenCalledWith ( 'graph TD\nA --' , { suppressErrors : true } )
85+ expect ( mermaidMocks . renderMock ) . not . toHaveBeenCalled ( )
86+ expect ( mermaidMocks . setParseErrorHandlerMock ) . toHaveBeenCalled ( )
87+ } )
88+
89+ it ( 'falls back to source and asks Mermaid not to inject its own error SVG when render throws' , async ( ) => {
90+ mermaidMocks . renderMock . mockRejectedValueOnce ( new Error ( 'render failed' ) )
91+ const code = 'gantt\ndateFormat YYYY-MM-DD\nsection A\nTask :a, 2024-01-01'
92+
93+ renderMermaid ( code )
94+
95+ await waitFor ( ( ) => {
96+ const fallback = document . querySelector ( '.aui-mermaid-fallback' )
97+ expect ( fallback ) . toBeTruthy ( )
98+ expect ( fallback ?. textContent ) . toBe ( code )
99+ } )
100+
101+ expect ( mermaidMocks . renderMock ) . toHaveBeenCalled ( )
102+ expect ( mermaidMocks . initializeMock ) . toHaveBeenCalledWith ( expect . objectContaining ( {
103+ suppressErrorRendering : true ,
104+ } ) )
105+ } )
106+
47107} )
0 commit comments