diff --git a/.changeset/brave-memes-flash.md b/.changeset/brave-memes-flash.md new file mode 100644 index 000000000..720cd7202 --- /dev/null +++ b/.changeset/brave-memes-flash.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Support edge animation in hand drawn look diff --git a/.changeset/busy-mirrors-try.md b/.changeset/busy-mirrors-try.md new file mode 100644 index 000000000..7e5d3b632 --- /dev/null +++ b/.changeset/busy-mirrors-try.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Resolved parsing error where direction TD was not recognized within subgraphs diff --git a/.changeset/curly-apes-prove.md b/.changeset/curly-apes-prove.md new file mode 100644 index 000000000..2acf3d1a3 --- /dev/null +++ b/.changeset/curly-apes-prove.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Improve participant parsing and prevent recursive loops on invalid syntax diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index ab4bbef64..cf0e9a87d 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -6,6 +6,7 @@ interface CypressConfig { listUrl?: boolean; listId?: string; name?: string; + screenshot?: boolean; } type CypressMermaidConfig = MermaidConfig & CypressConfig; @@ -90,7 +91,7 @@ export const renderGraph = ( export const openURLAndVerifyRendering = ( url: string, - options: CypressMermaidConfig, + { screenshot = true, ...options }: CypressMermaidConfig, validation?: any ): void => { const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-'); @@ -103,7 +104,9 @@ export const openURLAndVerifyRendering = ( cy.get('svg').should(validation); } - verifyScreenshot(name); + if (screenshot) { + verifyScreenshot(name); + } }; export const verifyScreenshot = (name: string): void => { diff --git a/cypress/integration/rendering/flowchart-handDrawn.spec.js b/cypress/integration/rendering/flowchart-handDrawn.spec.js index 49c55c628..d3ca1d1f1 100644 --- a/cypress/integration/rendering/flowchart-handDrawn.spec.js +++ b/cypress/integration/rendering/flowchart-handDrawn.spec.js @@ -1029,4 +1029,19 @@ graph TD } ); }); + + it('FDH49: should add edge animation', () => { + renderGraph( + ` + flowchart TD + A(["Start"]) L_A_B_0@--> B{"Decision"} + B --> C["Option A"] & D["Option B"] + style C stroke-width:4px,stroke-dasharray: 5 + L_A_B_0@{ animation: slow } + L_B_D_0@{ animation: fast }`, + { look: 'handDrawn', screenshot: false } + ); + cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow'); + cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast'); + }); }); diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js index 40713ac4e..5e1984377 100644 --- a/cypress/integration/rendering/flowchart.spec.js +++ b/cypress/integration/rendering/flowchart.spec.js @@ -774,6 +774,21 @@ describe('Graph', () => { expect(svg).to.not.have.attr('style'); }); }); + it('40: should add edge animation', () => { + renderGraph( + ` + flowchart TD + A(["Start"]) L_A_B_0@--> B{"Decision"} + B --> C["Option A"] & D["Option B"] + style C stroke-width:4px,stroke-dasharray: 5 + L_A_B_0@{ animation: slow } + L_B_D_0@{ animation: fast }`, + { screenshot: false } + ); + // Verify animation classes are applied to both edges + cy.get('path#L_A_B_0').should('have.class', 'edge-animation-slow'); + cy.get('path#L_B_D_0').should('have.class', 'edge-animation-fast'); + }); it('58: handle styling with style expressions', () => { imgSnapshotTest( ` @@ -973,4 +988,19 @@ graph TD } ); }); + + it('70: should render a subgraph with direction TD', () => { + imgSnapshotTest( + ` + flowchart LR + subgraph A + direction TD + a --> b + end + `, + { + fontFamily: 'courier', + } + ); + }); }); diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 955cc2a7b..110f4eff1 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -21,7 +21,7 @@ title: Animal example classDiagram note "From Duck till Zebra" Animal <|-- Duck - note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" + note for Duck "can fly
can swim
can dive
can help in debugging" Animal <|-- Fish Animal <|-- Zebra Animal : +int age @@ -50,7 +50,7 @@ title: Animal example classDiagram note "From Duck till Zebra" Animal <|-- Duck - note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" + note for Duck "can fly
can swim
can dive
can help in debugging" Animal <|-- Fish Animal <|-- Zebra Animal : +int age diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index 7b2f4386a..7340cf8d3 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -140,6 +140,7 @@ that id. .*direction\s+BT[^\n]* return 'direction_bt'; .*direction\s+RL[^\n]* return 'direction_rl'; .*direction\s+LR[^\n]* return 'direction_lr'; +.*direction\s+TD[^\n]* return 'direction_td'; [^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; } [0-9]+ return 'NUM'; @@ -626,6 +627,8 @@ direction { $$={stmt:'dir', value:'RL'};} | direction_lr { $$={stmt:'dir', value:'LR'};} + | direction_td + { $$={stmt:'dir', value:'TD'};} ; %% diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js index 9339a6e2c..1273c4d7f 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js @@ -309,4 +309,21 @@ describe('when parsing subgraphs', function () { expect(subgraphA.nodes).toContain('a'); expect(subgraphA.nodes).not.toContain('c'); }); + it('should correctly parse direction TD inside a subgraph', function () { + const res = flow.parser.parse(` + graph LR + subgraph WithTD + direction TD + A1 --> A2 + end + `); + + const subgraphs = flow.parser.yy.getSubGraphs(); + expect(subgraphs.length).toBe(1); + const subgraph = subgraphs[0]; + + expect(subgraph.dir).toBe('TD'); + expect(subgraph.nodes).toContain('A1'); + expect(subgraph.nodes).toContain('A2'); + }); }); diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index f1364895b..214189a5b 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -32,13 +32,14 @@ [^\}]+ { return 'CONFIG_CONTENT'; } \} { this.popState(); this.popState(); return 'CONFIG_END'; } [^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; } -[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +[^<>:\n,;@\s]+(?=\s+as\s) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } +[^<>:\n,;@]+(?=\s*[\n;#]|$) { yytext = yytext.trim(); this.popState(); return 'ACTOR'; } +[^<>:\n,;@]*\<[^\n]* { this.popState(); return 'INVALID'; } "box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } "actor" { this.begin('ID'); return 'participant_actor'; } "create" return 'create'; "destroy" { this.begin('ID'); return 'destroy'; } -[^<\->\->:\n,;]+?([\-]*[^<\->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { this.popState(); this.popState(); return 'NEWLINE'; } "loop" { this.begin('LINE'); return 'loop'; } @@ -145,6 +146,7 @@ line : SPACE statement { $$ = $2 } | statement { $$ = $1 } | NEWLINE { $$=[]; } + | INVALID { $$=[]; } ; box_section @@ -411,4 +413,4 @@ text2 : TXT {$$ = yy.parseMessage($1.trim().substring(1)) } ; -%% +%% \ No newline at end of file diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 5f4e06dcd..cabcd7db5 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -2609,5 +2609,17 @@ Bob->>Alice:Got it! expect(actors.get('E').type).toBe('entity'); expect(actors.get('E').description).toBe('E'); }); + it('should handle fail parsing when alias token causes conflicts in participant definition', async () => { + let error = false; + try { + await Diagram.fromText(` + sequenceDiagram + participant SAS MyServiceWithMoreThan20Chars
service decription + `); + } catch (e) { + error = true; + } + expect(error).toBe(true); + }); }); }); diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index e62433283..367030d8e 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -15,7 +15,7 @@ title: Animal example classDiagram note "From Duck till Zebra" Animal <|-- Duck - note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" + note for Duck "can fly
can swim
can dive
can help in debugging" Animal <|-- Fish Animal <|-- Zebra Animal : +int age diff --git a/packages/mermaid/src/rendering-util/rendering-elements/edges.js b/packages/mermaid/src/rendering-util/rendering-elements/edges.js index 9e308631a..81cd79d46 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/edges.js +++ b/packages/mermaid/src/rendering-util/rendering-elements/edges.js @@ -605,6 +605,14 @@ export const insertEdge = function ( const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style]; let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:')); + let animationClass = ''; + if (edge.animate) { + animationClass = 'edge-animation-fast'; + } + if (edge.animation) { + animationClass = 'edge-animation-' + edge.animation; + } + let animatedEdge = false; if (edge.look === 'handDrawn') { const rc = rough.svg(elem); @@ -620,7 +628,13 @@ export const insertEdge = function ( svgPath = select(svgPathNode) .select('path') .attr('id', edge.id) - .attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '')) + .attr( + 'class', + ' ' + + strokeClasses + + (edge.classes ? ' ' + edge.classes : '') + + (animationClass ? ' ' + animationClass : '') + ) .attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : ''); let d = svgPath.attr('d'); svgPath.attr('d', d); @@ -628,13 +642,6 @@ export const insertEdge = function ( } else { const stylesFromClasses = edgeClassStyles.join(';'); const styles = edgeStyles ? edgeStyles.reduce((acc, style) => acc + style + ';', '') : ''; - let animationClass = ''; - if (edge.animate) { - animationClass = ' edge-animation-fast'; - } - if (edge.animation) { - animationClass = ' edge-animation-' + edge.animation; - } const pathStyle = (stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles) + @@ -646,7 +653,10 @@ export const insertEdge = function ( .attr('id', edge.id) .attr( 'class', - ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : '') + (animationClass ?? '') + ' ' + + strokeClasses + + (edge.classes ? ' ' + edge.classes : '') + + (animationClass ? ' ' + animationClass : '') ) .attr('style', pathStyle);