import mermaid from './mermaid.js'; import { mermaidAPI } from './mermaidAPI.js'; import './diagram-api/diagram-orchestration.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import { beforeAll, describe, it, expect, vi } from 'vitest'; import type { DiagramDefinition } from './diagram-api/types.js'; beforeAll(async () => { addDiagrams(); }); const spyOn = vi.spyOn; vi.mock('./mermaidAPI.js'); afterEach(() => { vi.clearAllMocks(); }); describe('when using mermaid and ', () => { describe('when detecting chart type ', () => { it('should not start rendering with mermaid.startOnLoad set to false', async () => { mermaid.startOnLoad = false; document.body.innerHTML = '
graph TD;\na;
'; spyOn(mermaid, 'run'); mermaid.contentLoaded(); expect(mermaid.run).not.toHaveBeenCalled(); }); it('should start rendering with both startOnLoad set', async () => { mermaid.startOnLoad = true; document.body.innerHTML = '
graph TD;\na;
'; spyOn(mermaid, 'run'); mermaid.contentLoaded(); expect(mermaid.run).toHaveBeenCalled(); }); it('should start rendering with mermaid.startOnLoad', async () => { mermaid.startOnLoad = true; document.body.innerHTML = '
graph TD;\na;
'; spyOn(mermaid, 'run'); mermaid.contentLoaded(); expect(mermaid.run).toHaveBeenCalled(); }); it('should start rendering as a default with no changes performed', async () => { document.body.innerHTML = '
graph TD;\na;
'; spyOn(mermaid, 'run'); mermaid.contentLoaded(); expect(mermaid.run).toHaveBeenCalled(); }); }); describe('when using #run', () => { it('should accept single node', async () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('graph TD;\na;')); await mermaid.run({ nodes: [node], }); // mermaidAPI.render function has been mocked, since it doesn't yet work // in Node.JS (only works in browser) expect(mermaidAPI.render).toHaveBeenCalled(); }); }); describe('when using #registerExternalDiagrams', () => { it('should throw error (but still render) if registerExternalDiagrams fails', async () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('graph TD;\na;')); await expect( mermaid.registerExternalDiagrams( [ { id: 'dummyError', detector: (text) => /dummyError/.test(text), loader: () => Promise.reject('dummyError'), }, ], { lazyLoad: false } ) ).rejects.toThrow('Failed to load 1 external diagrams'); await expect( mermaid.run({ nodes: [node], }) ).resolves.not.toThrow(); // should still render, even if lazyLoadedDiagrams fails expect(mermaidAPI.render).toHaveBeenCalled(); }); it('should defer diagram load based on parameter', async () => { let loaded = false; const dummyDiagram: DiagramDefinition = { db: {}, renderer: { draw: () => { // no-op }, }, parser: { parse: (_text) => { return; }, parser: { yy: {} }, }, styles: () => { // do nothing }, }; await expect( mermaid.registerExternalDiagrams( [ { id: 'dummy', detector: (text) => /dummy/.test(text), loader: () => { loaded = true; return Promise.resolve({ id: 'dummy', diagram: dummyDiagram, }); }, }, ], { lazyLoad: true } ) ).resolves.toBe(undefined); expect(loaded).toBe(false); await expect( mermaid.registerExternalDiagrams( [ { id: 'dummy2', detector: (text) => /dummy2/.test(text), loader: () => { loaded = true; return Promise.resolve({ id: 'dummy2', diagram: dummyDiagram, }); }, }, ], { lazyLoad: false } ) ).resolves.toBe(undefined); expect(loaded).toBe(true); }); afterEach(() => { // we modify mermaid config in some tests, so we need to make sure to reset them mermaidAPI.reset(); }); }); describe('checking validity of input ', () => { it('should throw for an invalid definition', async () => { await expect( mermaid.parse('this is not a mermaid diagram definition') ).rejects.toThrowErrorMatchingInlineSnapshot( '"No diagram type detected matching given configuration for text: this is not a mermaid diagram definition"' ); }); it('should not throw for a valid flow definition', async () => { await expect( mermaid.parse('graph TD;A--x|text including URL space|B;') ).resolves.not.toThrow(); }); it('should throw for an invalid flow definition', async () => { await expect(mermaid.parse('graph TQ;A--x|text including URL space|B;')).rejects .toThrowErrorMatchingInlineSnapshot(` "Lexical error on line 1. Unrecognized text. graph TQ;A--x|text include -----^" `); }); it('should not throw for a valid sequenceDiagram definition (mmds1)', async () => { const text = 'sequenceDiagram\n' + 'Alice->Bob: Hello Bob, how are you?\n\n' + '%% Comment\n' + 'Note right of Bob: Bob thinks\n' + 'alt isWell\n\n' + 'Bob-->Alice: I am good thanks!\n' + 'else isSick\n' + 'Bob-->Alice: Feel sick...\n' + 'end'; await expect(mermaid.parse(text)).resolves.not.toThrow(); }); it('should throw for an invalid sequenceDiagram definition', async () => { const text = 'sequenceDiagram\n' + 'Alice:->Bob: Hello Bob, how are you?\n\n' + '%% Comment\n' + 'Note right of Bob: Bob thinks\n' + 'alt isWell\n\n' + 'Bob-->Alice: I am good thanks!\n' + 'else isSick\n' + 'Bob-->Alice: Feel sick...\n' + 'end'; await expect(mermaid.parse(text)).rejects.toThrowErrorMatchingInlineSnapshot(` "Parse error on line 2: ...equenceDiagramAlice:->Bob: Hello Bob, h... ----------------------^ Expecting 'SOLID_OPEN_ARROW', 'DOTTED_OPEN_ARROW', 'SOLID_ARROW', 'DOTTED_ARROW', 'SOLID_CROSS', 'DOTTED_CROSS', 'SOLID_POINT', 'DOTTED_POINT', got 'TXT'" `); }); it('should return false for invalid definition WITH a parseError() callback defined', async () => { let parseErrorWasCalled = false; mermaid.setParseErrorHandler(() => { parseErrorWasCalled = true; }); await expect( mermaid.parse('this is not a mermaid diagram definition') ).rejects.toThrowErrorMatchingInlineSnapshot( '"No diagram type detected matching given configuration for text: this is not a mermaid diagram definition"' ); expect(parseErrorWasCalled).toEqual(true); }); }); });