mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-21 16:29:40 +02:00
Merge pull request #4124 from mermaid-js/sidv/fixDetectorOrder
fix: Detector order
This commit is contained in:
@@ -46,9 +46,24 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnknownDiagramError(`No diagram type detected for text: ${text}`);
|
throw new UnknownDiagramError(
|
||||||
|
`No diagram type detected matching given configuration for text: ${text}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers lazy-loaded diagrams to Mermaid.
|
||||||
|
*
|
||||||
|
* The diagram function is loaded asynchronously, so that diagrams are only loaded
|
||||||
|
* if the diagram is detected.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Please note that the order of diagram detectors is important.
|
||||||
|
* The first detector to return `true` is the diagram that will be loaded
|
||||||
|
* and used, so put more specific detectors at the beginning!
|
||||||
|
*
|
||||||
|
* @param diagrams - Diagrams to lazy load, and their detectors, in order of importance.
|
||||||
|
*/
|
||||||
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
|
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {
|
||||||
for (const { id, detector, loader } of diagrams) {
|
for (const { id, detector, loader } of diagrams) {
|
||||||
addDetector(id, detector, loader);
|
addDetector(id, detector, loader);
|
||||||
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { it, describe, expect } from 'vitest';
|
||||||
|
import { detectType } from './detectType';
|
||||||
|
import { addDiagrams } from './diagram-orchestration';
|
||||||
|
|
||||||
|
describe('diagram-orchestration', () => {
|
||||||
|
it('should register diagrams', () => {
|
||||||
|
expect(() => detectType('graph TD; A-->B')).toThrow();
|
||||||
|
addDiagrams();
|
||||||
|
expect(detectType('graph TD; A-->B')).toBe('flowchart');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('proper diagram types should be detetced', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
addDiagrams();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ text: 'graph TD;', expected: 'flowchart' },
|
||||||
|
{ text: 'flowchart TD;', expected: 'flowchart-v2' },
|
||||||
|
{ text: 'flowchart-v2 TD;', expected: 'flowchart-v2' },
|
||||||
|
{ text: 'flowchart-elk TD;', expected: 'flowchart-elk' },
|
||||||
|
{ text: 'error', expected: 'error' },
|
||||||
|
{ text: 'C4Context;', expected: 'c4' },
|
||||||
|
{ text: 'classDiagram', expected: 'class' },
|
||||||
|
{ text: 'classDiagram-v2', expected: 'classDiagram' },
|
||||||
|
{ text: 'erDiagram', expected: 'er' },
|
||||||
|
{ text: 'journey', expected: 'journey' },
|
||||||
|
{ text: 'gantt', expected: 'gantt' },
|
||||||
|
{ text: 'pie', expected: 'pie' },
|
||||||
|
{ text: 'requirementDiagram', expected: 'requirement' },
|
||||||
|
{ text: 'info', expected: 'info' },
|
||||||
|
{ text: 'sequenceDiagram', expected: 'sequence' },
|
||||||
|
{ text: 'mindmap', expected: 'mindmap' },
|
||||||
|
{ text: 'timeline', expected: 'timeline' },
|
||||||
|
{ text: 'gitGraph', expected: 'gitGraph' },
|
||||||
|
{ text: 'stateDiagram', expected: 'state' },
|
||||||
|
{ text: 'stateDiagram-v2', expected: 'stateDiagram' },
|
||||||
|
])(
|
||||||
|
'should $text be detected as $expected',
|
||||||
|
({ text, expected }: { text: string; expected: string }) => {
|
||||||
|
expect(detectType(text)).toBe(expected);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should detect proper flowchart type based on config', () => {
|
||||||
|
// graph & dagre-d3 => flowchart
|
||||||
|
expect(detectType('graph TD; A-->B')).toBe('flowchart');
|
||||||
|
// graph & dagre-d3 => flowchart
|
||||||
|
expect(detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })).toBe(
|
||||||
|
'flowchart'
|
||||||
|
);
|
||||||
|
// flowchart & dagre-d3 => error
|
||||||
|
expect(() =>
|
||||||
|
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-d3' } })
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
'"No diagram type detected matching given configuration for text: flowchart TD; A-->B"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// graph & dagre-wrapper => flowchart-v2
|
||||||
|
expect(
|
||||||
|
detectType('graph TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
|
||||||
|
).toBe('flowchart-v2');
|
||||||
|
// flowchart ==> flowchart-v2
|
||||||
|
expect(detectType('flowchart TD; A-->B')).toBe('flowchart-v2');
|
||||||
|
// flowchart && dagre-wrapper ==> flowchart-v2
|
||||||
|
expect(
|
||||||
|
detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'dagre-wrapper' } })
|
||||||
|
).toBe('flowchart-v2');
|
||||||
|
// flowchart && elk ==> flowchart-elk
|
||||||
|
expect(detectType('flowchart TD; A-->B', { flowchart: { defaultRenderer: 'elk' } })).toBe(
|
||||||
|
'flowchart-elk'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not detect flowchart if pie contains flowchart', () => {
|
||||||
|
expect(
|
||||||
|
detectType(`pie title: "flowchart"
|
||||||
|
flowchart: 1 "pie" pie: 2 "pie"`)
|
||||||
|
).toBe('pie');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -45,7 +45,7 @@ export const addDiagrams = () => {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
'Diagrams beginning with --- are not valid. ' +
|
'Diagrams beginning with --- are not valid. ' +
|
||||||
'If you were trying to use a YAML front-matter, please ensure that ' +
|
'If you were trying to use a YAML front-matter, please ensure that ' +
|
||||||
"you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
|
"you've correctly opened and closed the YAML front-matter with un-indented `---` blocks"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -55,25 +55,26 @@ export const addDiagrams = () => {
|
|||||||
return text.toLowerCase().trimStart().startsWith('---');
|
return text.toLowerCase().trimStart().startsWith('---');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
// Ordering of detectors is important. The first one to return true will be used.
|
||||||
registerLazyLoadedDiagrams(
|
registerLazyLoadedDiagrams(
|
||||||
error,
|
error,
|
||||||
c4,
|
c4,
|
||||||
classDiagram,
|
|
||||||
classDiagramV2,
|
classDiagramV2,
|
||||||
|
classDiagram,
|
||||||
er,
|
er,
|
||||||
gantt,
|
gantt,
|
||||||
info,
|
info,
|
||||||
pie,
|
pie,
|
||||||
requirement,
|
requirement,
|
||||||
sequence,
|
sequence,
|
||||||
flowchart,
|
|
||||||
flowchartV2,
|
|
||||||
flowchartElk,
|
flowchartElk,
|
||||||
|
flowchartV2,
|
||||||
|
flowchart,
|
||||||
mindmap,
|
mindmap,
|
||||||
timeline,
|
timeline,
|
||||||
git,
|
git,
|
||||||
state,
|
|
||||||
stateV2,
|
stateV2,
|
||||||
|
state,
|
||||||
journey
|
journey
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -20,8 +20,8 @@ describe('DiagramAPI', () => {
|
|||||||
|
|
||||||
it('should handle diagram registrations', () => {
|
it('should handle diagram registrations', () => {
|
||||||
expect(() => getDiagram('loki')).toThrow();
|
expect(() => getDiagram('loki')).toThrow();
|
||||||
expect(() => detectType('loki diagram')).toThrow(
|
expect(() => detectType('loki diagram')).toThrowErrorMatchingInlineSnapshot(
|
||||||
'No diagram type detected for text: loki diagram'
|
'"No diagram type detected matching given configuration for text: loki diagram"'
|
||||||
);
|
);
|
||||||
const detector: DiagramDetector = (str: string) => {
|
const detector: DiagramDetector = (str: string) => {
|
||||||
return str.match('loki') !== null;
|
return str.match('loki') !== null;
|
||||||
|
@@ -61,8 +61,8 @@ Expecting 'TXT', got 'NEWLINE'"
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should throw the right error for unregistered diagrams', async () => {
|
test('should throw the right error for unregistered diagrams', async () => {
|
||||||
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowError(
|
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
'No diagram type detected for text: thor TD; A-->B'
|
'"No diagram type detected matching given configuration for text: thor TD; A-->B"'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,15 +4,15 @@ import type { ExternalDiagramDefinition } from '../../diagram-api/types';
|
|||||||
const id = 'flowchart-v2';
|
const id = 'flowchart-v2';
|
||||||
|
|
||||||
const detector: DiagramDetector = (txt, config) => {
|
const detector: DiagramDetector = (txt, config) => {
|
||||||
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
|
if (
|
||||||
return false;
|
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
|
||||||
}
|
config?.flowchart?.defaultRenderer === 'elk'
|
||||||
if (config?.flowchart?.defaultRenderer === 'elk') {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
|
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
|
||||||
if (txt.match(/^\s*graph/) !== null) {
|
if (txt.match(/^\s*graph/) !== null && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return txt.match(/^\s*flowchart/) !== null;
|
return txt.match(/^\s*flowchart/) !== null;
|
||||||
|
@@ -5,10 +5,10 @@ const id = 'flowchart';
|
|||||||
const detector: DiagramDetector = (txt, config) => {
|
const detector: DiagramDetector = (txt, config) => {
|
||||||
// If we have conferred to only use new flow charts this function should always return false
|
// If we have conferred to only use new flow charts this function should always return false
|
||||||
// as in not signalling true for a legacy flowchart
|
// as in not signalling true for a legacy flowchart
|
||||||
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
if (
|
||||||
return false;
|
config?.flowchart?.defaultRenderer === 'dagre-wrapper' ||
|
||||||
}
|
config?.flowchart?.defaultRenderer === 'elk'
|
||||||
if (config?.flowchart?.defaultRenderer === 'elk') {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return txt.match(/^\s*graph/) !== null;
|
return txt.match(/^\s*graph/) !== null;
|
||||||
|
@@ -12,6 +12,7 @@ import type { ParseErrorFunction } from './Diagram';
|
|||||||
import { isDetailedError } from './utils';
|
import { isDetailedError } from './utils';
|
||||||
import type { DetailedError } from './utils';
|
import type { DetailedError } from './utils';
|
||||||
import { ExternalDiagramDefinition } from './diagram-api/types';
|
import { ExternalDiagramDefinition } from './diagram-api/types';
|
||||||
|
import { UnknownDiagramError } from './errors';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
MermaidConfig,
|
MermaidConfig,
|
||||||
@@ -20,6 +21,7 @@ export type {
|
|||||||
ParseErrorFunction,
|
ParseErrorFunction,
|
||||||
RenderResult,
|
RenderResult,
|
||||||
ParseOptions,
|
ParseOptions,
|
||||||
|
UnknownDiagramError,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface RunOptions {
|
export interface RunOptions {
|
||||||
|
@@ -666,7 +666,7 @@ describe('mermaidAPI', () => {
|
|||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
'Diagrams beginning with --- are not valid. ' +
|
'Diagrams beginning with --- are not valid. ' +
|
||||||
'If you were trying to use a YAML front-matter, please ensure that ' +
|
'If you were trying to use a YAML front-matter, please ensure that ' +
|
||||||
"you've correctly opened and closed the YAML front-matter with unindented `---` blocks"
|
"you've correctly opened and closed the YAML front-matter with un-indented `---` blocks"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('does not throw for a valid definition', async () => {
|
it('does not throw for a valid definition', async () => {
|
||||||
@@ -678,7 +678,7 @@ describe('mermaidAPI', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
mermaidAPI.parse('this is not a mermaid diagram definition')
|
mermaidAPI.parse('this is not a mermaid diagram definition')
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
'"No diagram type detected for text: this is not a mermaid diagram definition"'
|
'"No diagram type detected matching given configuration for text: this is not a mermaid diagram definition"'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('returns false for invalid definition with silent option', async () => {
|
it('returns false for invalid definition with silent option', async () => {
|
||||||
|
Reference in New Issue
Block a user