diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js index df1fac0cd..faa511626 100644 --- a/cypress/integration/rendering/erDiagram.spec.js +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -10,7 +10,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render an ER diagram with a recursive relationship', () => { @@ -23,7 +22,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render an ER diagram with multiple relationships between the same two entities', () => { @@ -35,7 +33,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render a cyclical ER diagram', () => { @@ -48,7 +45,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render a not-so-simple ER diagram', () => { @@ -66,7 +62,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render multiple ER diagrams', () => { @@ -85,7 +80,6 @@ describe('Entity Relationship Diagram', () => { ], { logLevel: 1 } ); - cy.get('svg'); }); it('should render an ER diagram with blank or empty labels', () => { @@ -98,7 +92,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render an ER diagrams when useMaxWidth is true (default)', () => { @@ -151,7 +144,6 @@ describe('Entity Relationship Diagram', () => { `, { er: { useMaxWidth: false } } ); - cy.get('svg'); }); it('should render entities with and without attributes', () => { @@ -164,7 +156,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with generic and array attributes', () => { @@ -179,7 +170,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with length in attributes type', () => { @@ -193,7 +183,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities and attributes with big and small entity names', () => { @@ -209,7 +198,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with keys', () => { @@ -228,7 +216,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with comments', () => { @@ -247,7 +234,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with keys and comments', () => { @@ -267,7 +253,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('should render entities with aliases', () => { @@ -285,7 +270,6 @@ describe('Entity Relationship Diagram', () => { `, { logLevel: 1 } ); - cy.get('svg'); }); it('1433: should render a simple ER diagram with a title', () => { diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js new file mode 100644 index 000000000..e837565d3 --- /dev/null +++ b/cypress/integration/rendering/errorDiagram.spec.js @@ -0,0 +1,45 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +describe('Error Diagrams', () => { + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + expect(err.message).to.include('Parse error'); + // return false to prevent the error from + // failing this test + return false; + }); + }); + + it('should render a simple ER diagram', () => { + imgSnapshotTest( + ` + error + `, + { logLevel: 1 } + ); + }); + + it('should render error diagram for actual errors', () => { + imgSnapshotTest( + ` + flowchart TD + A[Christmas] --|Get money| B(Go shopping) + `, + { logLevel: 1 } + ); + }); + + it('should render error for wrong ER diagram', () => { + imgSnapshotTest( + ` + erDiagram + ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many" + ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many" + ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many" + MONGODB-CLUSTERS ||..|{ + ATLAS-TEAMS ||..|{ + `, + { logLevel: 1 } + ); + }); +}); diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js index 2e1093519..99533192d 100644 --- a/cypress/platform/viewer.js +++ b/cypress/platform/viewer.js @@ -47,7 +47,6 @@ const contentLoaded = async function () { await mermaid2.registerExternalDiagrams([externalExample]); mermaid2.initialize(graphObj.mermaid); await mermaid2.run(); - markRendered(); } }; @@ -123,7 +122,6 @@ const contentLoadedApi = async function () { bindFunctions(div); } } - markRendered(); }; if (typeof document !== 'undefined') { @@ -135,10 +133,10 @@ if (typeof document !== 'undefined') { function () { if (this.location.href.match('xss.html')) { this.console.log('Using api'); - void contentLoadedApi(); + void contentLoadedApi().finally(markRendered); } else { this.console.log('Not using api'); - void contentLoaded(); + void contentLoaded().finally(markRendered); } }, false diff --git a/demos/error.html b/demos/error.html new file mode 100644 index 000000000..2d6d1b01f --- /dev/null +++ b/demos/error.html @@ -0,0 +1,38 @@ + + +
+ + ++ erDiagram + ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many" + ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many" + ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many" ++
+ erDiagram + ATLAS-ORGANIZATION ||--|{ ATLAS-PROJECTS : "has many" + ATLAS-PROJECTS ||--|{ MONGODB-CLUSTERS : "has many" + ATLAS-PROJECTS ||--|{ ATLAS-TEAMS : "has many" + MONGODB-CLUSTERS ||..|{ + ATLAS-TEAMS ||..|{ ++
+ flowchart TD + A[Christmas] -->|Get money| B(Go shopping) ++
+ flowchart TD + A[Christmas] --|Get money| B(Go shopping) ++ + + diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index a039fba38..5a5a63786 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:663](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L663) +[mermaidAPI.ts:659](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L659) ## Functions diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 3010ce130..dd26d8de8 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -5,6 +5,7 @@ import { detectType, getDiagramLoader } from './diagram-api/detectType'; import { extractFrontMatter } from './diagram-api/frontmatter'; import { UnknownDiagramError } from './errors'; import { DetailedError } from './utils'; +import { cleanupComments } from './diagram-api/comments'; export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void; @@ -43,7 +44,8 @@ export class Diagram { // Similarly, we can't do this in getDiagramFromText() because some code // calls diagram.db.clear(), which would reset anything set by // extractFrontMatter(). - this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db)); + this.parser.parse = (text: string) => + originalParse(cleanupComments(extractFrontMatter(text, this.db))); this.parser.parser.yy = this.db; if (diagram.init) { diagram.init(cnf); diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts new file mode 100644 index 000000000..a2c896079 --- /dev/null +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -0,0 +1,94 @@ +// tests to check that comments are removed + +import { cleanupComments } from './comments'; +import { describe, it, expect } from 'vitest'; + +describe('comments', () => { + it('should remove comments', () => { + const text = ` + +%% This is a comment +%% This is another comment +graph TD + A-->B +%% This is a comment +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + " + `); + }); + + it('should keep init statements when removing comments', () => { + const text = ` +%% This is a comment + +%% This is another comment +%%{init: {'theme': 'forest'}}%% +%%{ init: {'theme': 'space before init'}}%% +%%{init: {'theme': 'space after ending'}}%% +graph TD + A-->B + + B-->C +%% This is a comment +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "%%{init: {'theme': 'forest'}}%% + %%{ init: {'theme': 'space before init'}}%% + %%{init: {'theme': 'space after ending'}}%% + graph TD + A-->B + + B-->C + " + `); + }); + + it('should remove indented comments', () => { + const text = ` +%% This is a comment +graph TD + A-->B + %% This is a comment + C-->D +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + C-->D + " + `); + }); + + it('should remove empty newlines from start', () => { + const text = ` + + + + +%% This is a comment +graph TD + A-->B +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + " + `); + }); + + it('should remove comments at end of text with no newline', () => { + const text = ` +graph TD + A-->B +%% This is a comment`; + + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + " + `); + }); +}); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts new file mode 100644 index 000000000..be39b0a0f --- /dev/null +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -0,0 +1,8 @@ +/** + * Remove all lines starting with `%%` from the text that don't contain a `%%{` + * @param text - The text to remove comments from + * @returns cleaned text + */ +export const cleanupComments = (text: string): string => { + return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); +}; diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 73bfcf084..a88a34f19 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -13,7 +13,7 @@ import classDiagramV2 from '../diagrams/class/classDetector-V2'; import state from '../diagrams/state/stateDetector'; import stateV2 from '../diagrams/state/stateDetector-V2'; import journey from '../diagrams/user-journey/journeyDetector'; -import error from '../diagrams/error/errorDetector'; +import errorDiagram from '../diagrams/error/errorDiagram'; import flowchartElk from '../diagrams/flowchart/elk/detector'; import timeline from '../diagrams/timeline/detector'; import mindmap from '../diagrams/mindmap/detector'; @@ -28,6 +28,9 @@ export const addDiagrams = () => { // This is added here to avoid race-conditions. // We could optimize the loading logic somehow. hasLoadedDiagrams = true; + registerDiagram('error', errorDiagram, (text) => { + return text.toLowerCase().trim() === 'error'; + }); registerDiagram( '---', // --- diagram type may appear if YAML front-matter is not parsed correctly @@ -57,7 +60,6 @@ export const addDiagrams = () => { ); // Ordering of detectors is important. The first one to return true will be used. registerLazyLoadedDiagrams( - error, c4, classDiagramV2, classDiagram, diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index d9f03c387..8ffa87c63 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -19,8 +19,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili