From e7b4f7f6cae45816049c2121b42c26af7e63a9f8 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 13:33:51 -0800 Subject: [PATCH 01/45] trim state ids as they are processed by docTranslator --- .../mermaid/src/diagrams/state/stateDb.js | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index 991aba078..81b8ffb8b 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -94,9 +94,14 @@ const docTranslator = (parent, node, first) => { docTranslator(parent, node.state1, true); docTranslator(parent, node.state2, false); } else { - if (node.stmt === STMT_STATE && node.id === '[*]') { - node.id = first ? parent.id + '_start' : parent.id + '_end'; - node.start = first; + if (node.stmt === STMT_STATE) { + if (node.id === '[*]') { + node.id = first ? parent.id + '_start' : parent.id + '_end'; + node.start = first; + } else { + // This is just a plain state, not a start or end + node.id = node.id.trim(); + } } if (node.doc) { @@ -170,7 +175,7 @@ const extract = (_doc) => { switch (item.stmt) { case STMT_STATE: addState( - item.id, + item.id.trim(), item.type, item.doc, item.description, @@ -184,10 +189,10 @@ const extract = (_doc) => { addRelation(item.state1, item.state2, item.description); break; case STMT_CLASSDEF: - addStyleClass(item.id, item.classes); + addStyleClass(item.id.trim(), item.classes); break; case STMT_APPLYCLASS: - setCssClass(item.id, item.styleClass); + setCssClass(item.id.trim(), item.styleClass); break; } }); @@ -215,11 +220,12 @@ export const addState = function ( styles = null, textStyles = null ) { + const trimmedId = id?.trim(); // add the state if needed - if (currentDocument.states[id] === undefined) { - log.info('Adding state ', id, descr); - currentDocument.states[id] = { - id: id, + if (currentDocument.states[trimmedId] === undefined) { + log.info('Adding state ', trimmedId, descr); + currentDocument.states[trimmedId] = { + id: trimmedId, descriptions: [], type, doc, @@ -229,49 +235,49 @@ export const addState = function ( textStyles: [], }; } else { - if (!currentDocument.states[id].doc) { - currentDocument.states[id].doc = doc; + if (!currentDocument.states[trimmedId].doc) { + currentDocument.states[trimmedId].doc = doc; } - if (!currentDocument.states[id].type) { - currentDocument.states[id].type = type; + if (!currentDocument.states[trimmedId].type) { + currentDocument.states[trimmedId].type = type; } } if (descr) { - log.info('Setting state description', id, descr); + log.info('Setting state description', trimmedId, descr); if (typeof descr === 'string') { - addDescription(id, descr.trim()); + addDescription(trimmedId, descr.trim()); } if (typeof descr === 'object') { - descr.forEach((des) => addDescription(id, des.trim())); + descr.forEach((des) => addDescription(trimmedId, des.trim())); } } if (note) { - currentDocument.states[id].note = note; - currentDocument.states[id].note.text = common.sanitizeText( - currentDocument.states[id].note.text, + currentDocument.states[trimmedId].note = note; + currentDocument.states[trimmedId].note.text = common.sanitizeText( + currentDocument.states[trimmedId].note.text, configApi.getConfig() ); } if (classes) { - log.info('Setting state classes', id, classes); + log.info('Setting state classes', trimmedId, classes); const classesList = typeof classes === 'string' ? [classes] : classes; - classesList.forEach((klass) => setCssClass(id, klass.trim())); + classesList.forEach((klass) => setCssClass(trimmedId, klass.trim())); } if (styles) { - log.info('Setting state styles', id, styles); + log.info('Setting state styles', trimmedId, styles); const stylesList = typeof styles === 'string' ? [styles] : styles; - stylesList.forEach((style) => setStyle(id, style.trim())); + stylesList.forEach((style) => setStyle(trimmedId, style.trim())); } if (textStyles) { - log.info('Setting state styles', id, styles); + log.info('Setting state styles', trimmedId, styles); const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles; - textStylesList.forEach((textStyle) => setTextStyle(id, textStyle.trim())); + textStylesList.forEach((textStyle) => setTextStyle(trimmedId, textStyle.trim())); } }; @@ -368,10 +374,10 @@ function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) { * @param relationTitle */ export function addRelationObjs(item1, item2, relationTitle) { - let id1 = startIdIfNeeded(item1.id); - let type1 = startTypeIfNeeded(item1.id, item1.type); - let id2 = startIdIfNeeded(item2.id); - let type2 = startTypeIfNeeded(item2.id, item2.type); + let id1 = startIdIfNeeded(item1.id.trim()); + let type1 = startTypeIfNeeded(item1.id.trim(), item1.type); + let id2 = startIdIfNeeded(item2.id.trim()); + let type2 = startTypeIfNeeded(item2.id.trim(), item2.type); addState( id1, @@ -412,9 +418,9 @@ export const addRelation = function (item1, item2, title) { if (typeof item1 === 'object') { addRelationObjs(item1, item2, title); } else { - const id1 = startIdIfNeeded(item1); + const id1 = startIdIfNeeded(item1.trim()); const type1 = startTypeIfNeeded(item1); - const id2 = endIdIfNeeded(item2); + const id2 = endIdIfNeeded(item2.trim()); const type2 = endTypeIfNeeded(item2); addState(id1, type1); From 2b7aa3f99d838892775da69c98985741fb542432 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 13:35:23 -0800 Subject: [PATCH 02/45] start of parser spec; test for this bug --- .../state/parser/state-parser.spec.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/mermaid/src/diagrams/state/parser/state-parser.spec.js diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js new file mode 100644 index 000000000..614e43491 --- /dev/null +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -0,0 +1,49 @@ +import stateDb from '../stateDb'; +import stateDiagram from './stateDiagram'; +import { setConfig } from '../../../config'; + +setConfig({ + securityLevel: 'strict', +}); + +describe('state parser can parse...', () => { + beforeEach(function () { + stateDiagram.parser.yy = stateDb; + stateDiagram.parser.yy.clear(); + }); + + describe('groups (clusters/containers)', () => { + it('state "Group Name" as stateIdentifier', () => { + const diagramText = `stateDiagram-v2 + state "Small State 1" as namedState1 + %% Notice that this is named "Big State 1" with an "as" + state "Big State 1" as bigState1 { + bigState1InternalState + } + namedState1 --> bigState1: should point to \\nBig State 1 container + + state "Small State 2" as namedState2 + %% Notice that bigState2 does not have a name; no "as" + state bigState2 { + bigState2InternalState + } + namedState2 --> bigState2: should point to \\nbigState2 container`; + + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDiagram.parser.yy.getStates(); + expect(states['namedState1']).not.toBeUndefined(); + expect(states['bigState1']).not.toBeUndefined(); + expect(states['bigState1'].doc[0].id).toEqual('bigState1InternalState'); + expect(states['namedState2']).not.toBeUndefined(); + expect(states['bigState2']).not.toBeUndefined(); + expect(states['bigState2'].doc[0].id).toEqual('bigState2InternalState'); + const relationships = stateDiagram.parser.yy.getRelations(); + expect(relationships[0].id1).toEqual('namedState1'); + expect(relationships[0].id2).toEqual('bigState1'); + expect(relationships[1].id1).toEqual('namedState2'); + expect(relationships[1].id2).toEqual('bigState2'); + }); + }); +}); From ae25a08fe3c9375dc5096c9d0c58f1f3b1733981 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 13:35:44 -0800 Subject: [PATCH 03/45] (minor) add JSDOC comments --- packages/mermaid/src/diagrams/state/stateRenderer-v2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index fa2470d6b..bdba95d7e 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -307,8 +307,8 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) => * * @param g * @param parentParsedItem - parsed Item that is the parent of this document (doc) - * @param doc - the document to set up - * @param {object} diagramStates - the list of all known states for the diagram + * @param doc - the document to set up; it is a list of parsed statements + * @param {object[]} diagramStates - the list of all known states for the diagram * @param diagramDb * @param {boolean} altFlag * @todo This duplicates some of what is done in stateDb.js extract method From f0b8657423b22046c64b10fd20a6e9ede224534c Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 16:01:13 -0800 Subject: [PATCH 04/45] state demo: AS descriptions for states in composites --- demos/state.html | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/demos/state.html b/demos/state.html index 7aaa7516a..d80e239e7 100644 --- a/demos/state.html +++ b/demos/state.html @@ -161,12 +161,19 @@ title: Very simple diagram First --> Second First --> Third - state First { + state "the first composite" as First { [*] --> 1st - 1st --> [*] + state innerFirst { + state "1 in innerFirst" as 1st1st + 1st2nd: 2 in innerFirst + [*] --> 1st1st + 1st1st --> 1st2nd + %% 1st2nd --> 1st + } + 1st --> innerFirst + innerFirst --> 2nd } state Second { - [*] --> 2nd 2nd --> [*] } state Third { From d666981599ba0636811b705877ad082bcb4b28c6 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 16:21:28 -0800 Subject: [PATCH 05/45] parser: remove extra popState; whitespace formatting; remove 'zxzx' weird string --- .../diagrams/state/parser/stateDiagram.jison | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 67073210b..74707c5d5 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -65,14 +65,14 @@ \%%[^\n]* /* skip comments */ "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; -\s+"width" {this.popState();} +\s+"width" {this.popState(); } accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -[\}] { this.popState(); } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline"); } +[\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; "classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; } @@ -81,57 +81,60 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili [^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' } "class"\s+ { this.pushState('CLASS'); return 'class'; } -(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' } +(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' } [^\n]* { this.popState(); return 'STYLECLASS' } "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; \s+"width" {this.popState();} +"state"\s+ { /* console.log('Starting STATE '); */ this.pushState('STATE'); } -"state"\s+ { /*console.log('Starting STATE zxzx'+yy.getDirection());*/this.pushState('STATE'); } .*"<>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';} .*"<>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} -.*"<>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} +.*"<>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} .*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';} .*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} -.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} +.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} + .*direction\s+TB[^\n]* { return 'direction_tb';} .*direction\s+BT[^\n]* { return 'direction_bt';} .*direction\s+RL[^\n]* { return 'direction_rl';} .*direction\s+LR[^\n]* { return 'direction_lr';} -["] { /*console.log('Starting STATE_STRING zxzx');*/this.begin("STATE_STRING");} -\s*"as"\s+ {this.popState();this.pushState('STATE_ID');return "AS";} -[^\n\{]* {this.popState();/* console.log('STATE_ID', yytext);*/return "ID";} -["] this.popState(); -[^"]* { /*console.log('Long description:', yytext);*/return "STATE_DESCR";} -[^\n\s\{]+ {/*console.log('COMPOSIT_STATE', yytext);*/return 'COMPOSIT_STATE';} -\n {this.popState();} -\{ {this.popState();this.pushState('struct'); /*console.log('begin struct', yytext);*/return 'STRUCT_START';} -\%\%(?!\{)[^\n]* /* skip comments inside state*/ -\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';}} -[\n] /* nothing */ +["] { /* console.log('Starting STATE_STRING'); */ this.pushState("STATE_STRING"); } +\s*"as"\s+ { this.pushState('STATE_ID'); /* console.log('pushState(STATE_ID)'); */ return "AS"; } +[^\n\{]* { this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; } +["] { this.popState(); } +[^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; } +[^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; } +\n { this.popState(); } +\{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; } +\%\%(?!\{)[^\n]* /* skip comments inside state*/ +\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} } +[\n] /* nothing */ "note"\s+ { this.begin('NOTE'); return 'note'; } -"left of" { this.popState();this.pushState('NOTE_ID');return 'left_of';} -"right of" { this.popState();this.pushState('NOTE_ID');return 'right_of';} -\" { this.popState();this.pushState('FLOATING_NOTE');} -\s*"as"\s* {this.popState();this.pushState('FLOATING_NOTE_ID');return "AS";} -["] /**/ -[^"]* { /*console.log('Floating note text: ', yytext);*/return "NOTE_TEXT";} -[^\n]* {this.popState();/*console.log('Floating note ID', yytext);*/return "ID";} -\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');/*console.log('Got ID for note', yytext);*/return 'ID';} -\s*":"[^:\n;]+ { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim();return 'NOTE_TEXT';} -[\s\S]*?"end note" { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim();return 'NOTE_TEXT';} +"left of" { this.popState(); this.pushState('NOTE_ID'); return 'left_of'; } +"right of" { this.popState(); this.pushState('NOTE_ID'); return 'right_of'; } +\" { this.popState(); this.pushState('FLOATING_NOTE'); } +\s*"as"\s* { this.popState(); this.pushState('FLOATING_NOTE_ID'); return "AS"; } +["] /**/ +[^"]* { /* console.log('Floating note text: ', yytext); */ return "NOTE_TEXT"; } +[^\n]* { this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; } +\s*[^:\n\s\-]+ { this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; } +\s*":"[^:\n;]+ { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim(); return 'NOTE_TEXT'; } +[\s\S]*?"end note" { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim(); return 'NOTE_TEXT'; } -"stateDiagram"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; } -"stateDiagram-v2"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; } -"hide empty description" { /*console.log('HIDE_EMPTY', yytext,'#');*/return 'HIDE_EMPTY'; } -"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';} -[^:\n\s\-\{]+ { /*console.log('=>ID=',yytext);*/ return 'ID';} -// \s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } -\s*":"[^:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } +"stateDiagram"\s+ { /* console.log('Got state diagram', yytext,'#'); */ return 'SD'; } +"stateDiagram-v2"\s+ { /* console.log('Got state diagram', yytext,'#'); */ return 'SD'; } + +"hide empty description" { /* console.log('HIDE_EMPTY', yytext,'#'); */ return 'HIDE_EMPTY'; } + +"[*]" { /* console.log('EDGE_STATE=',yytext); */ return 'EDGE_STATE'; } +[^:\n\s\-\{]+ { /* console.log('=>ID=',yytext); */ return 'ID'; } +// \s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; } +\s*":"[^:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; } "-->" return '-->'; "--" return 'CONCURRENT'; @@ -201,7 +204,7 @@ statement | COMPOSIT_STATE | COMPOSIT_STATE STRUCT_START document STRUCT_STOP { - /* console.log('Adding document for state without id ', $1); */ + // console.log('Adding document for state without id ', $1); $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 } } | STATE_DESCR AS ID { @@ -217,7 +220,7 @@ statement } | STATE_DESCR AS ID STRUCT_START document STRUCT_STOP { - /* console.log('Adding document for state with id zxzx', $3, $4, yy.getDirection()); yy.addDocument($3);*/ + // console.log('state with id ', $3,' document = ', $5, ); $$={ stmt: 'state', id: $3, type: 'default', description: $1, doc: $5 } } | FORK { From ae7fd777a78ecc4a40a48d7675495926f0b85ea6 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Fri, 9 Dec 2022 16:22:56 -0800 Subject: [PATCH 06/45] + parsing specs that actually check results --- .../state/parser/state-parser.spec.js | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js index 614e43491..5ec5642e1 100644 --- a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -12,6 +12,72 @@ describe('state parser can parse...', () => { stateDiagram.parser.yy.clear(); }); + describe('states with id displayed as a (name)', () => { + describe('syntax 1: stateID as "name in quotes"', () => { + it('stateID as "some name"', () => { + const diagramText = `stateDiagram-v2 + state "Small State 1" as namedState1`; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDiagram.parser.yy.getStates(); + expect(states['namedState1']).not.toBeUndefined(); + expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1'); + }); + }); + + describe('syntax 2: stateID: "name in quotes" [colon after the id]', () => { + it('space before and after the colon', () => { + const diagramText = `stateDiagram-v2 + namedState1 : Small State 1`; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDiagram.parser.yy.getStates(); + expect(states['namedState1']).not.toBeUndefined(); + expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1'); + }); + + it('no spaces before and after the colon', () => { + const diagramText = `stateDiagram-v2 + namedState1:Small State 1`; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDiagram.parser.yy.getStates(); + expect(states['namedState1']).not.toBeUndefined(); + expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1'); + }); + }); + }); + + describe('can handle "as" in a state name', () => { + it('assemble, assemblies, state assemble, state assemblies', function () { + const diagramText = `stateDiagram-v2 + assemble + assemblies + state assemble + state assemblies + `; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + const states = stateDiagram.parser.yy.getStates(); + expect(states['assemble']).not.toBeUndefined(); + expect(states['assemblies']).not.toBeUndefined(); + }); + + it('state "as" as as', function () { + const diagramText = `stateDiagram-v2 + state "as" as as + `; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + const states = stateDiagram.parser.yy.getStates(); + expect(states['as']).not.toBeUndefined(); + expect(states['as'].descriptions.join(' ')).toEqual('as'); + }); + }); + describe('groups (clusters/containers)', () => { it('state "Group Name" as stateIdentifier', () => { const diagramText = `stateDiagram-v2 @@ -45,5 +111,23 @@ describe('state parser can parse...', () => { expect(relationships[1].id1).toEqual('namedState2'); expect(relationships[1].id2).toEqual('bigState2'); }); + + it('group has a state with stateID AS "state name" and state2ID: "another state name"', () => { + const diagramText = `stateDiagram-v2 + state "Big State 1" as bigState1 { + state "inner state 1" as inner1 + inner2: inner state 2 + inner1 --> inner2 + }`; + stateDiagram.parser.parse(diagramText); + stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2()); + + const states = stateDiagram.parser.yy.getStates(); + expect(states['bigState1']).not.toBeUndefined(); + expect(states['bigState1'].doc[0].id).toEqual('inner1'); + expect(states['bigState1'].doc[0].description).toEqual('inner state 1'); + expect(states['bigState1'].doc[1].id).toEqual('inner2'); + expect(states['bigState1'].doc[1].description).toEqual('inner state 2'); + }); }); }); From 521a30dcd7dc1fa1041d85c30d1584f93053b6e8 Mon Sep 17 00:00:00 2001 From: Ashley Engelund Date: Fri, 16 Dec 2022 06:37:05 -0800 Subject: [PATCH 07/45] add a space near the start for symmetry and readability Co-authored-by: Sidharth Vinod --- packages/mermaid/src/diagrams/state/parser/stateDiagram.jison | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 74707c5d5..dc050b2ff 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -65,7 +65,7 @@ \%%[^\n]* /* skip comments */ "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; -\s+"width" {this.popState(); } +\s+"width" { this.popState(); } accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } From e659601e03baac0a6d676f248db3e742495d5de9 Mon Sep 17 00:00:00 2001 From: Olivier Leveau Date: Thu, 5 Jan 2023 15:40:40 +0100 Subject: [PATCH 08/45] Add Box support in Sequence Diagrams --- .../rendering/sequencediagram.spec.js | 39 +++++++ demos/sequence.html | 16 +++ .../sequence/parser/sequenceDiagram.jison | 32 ++++- .../src/diagrams/sequence/sequenceDb.js | 110 +++++++++++++++++- .../diagrams/sequence/sequenceDiagram.spec.js | 41 +++++++ .../src/diagrams/sequence/sequenceRenderer.ts | 108 +++++++++++++++-- .../mermaid/src/diagrams/sequence/svgDraw.js | 42 +++++-- .../src/docs/syntax/sequenceDiagram.md | 32 +++++ 8 files changed, 393 insertions(+), 27 deletions(-) diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index b5ff92c8c..4e22bb99c 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -3,6 +3,45 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util'; context('Sequence diagram', () => { + it('should render a sequence diagram with boxes', () => { + renderGraph( + ` + sequenceDiagram + box LightGrey Alice and Bob + participant Alice + participant Bob + end + participant John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: false } } + ); + cy.get('svg').should((svg) => { + // const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + // expect(height).to.be.within(920, 971); + // use within because the absolute value can be slightly different depending on the environment ±5% + expect(width).to.be.within(830 * 0.95, 830 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); it('should render a simple sequence diagram', () => { imgSnapshotTest( ` diff --git a/demos/sequence.html b/demos/sequence.html index 2000d489a..891dc90ba 100644 --- a/demos/sequence.html +++ b/demos/sequence.html @@ -128,6 +128,22 @@
+
+      sequenceDiagram
+      box lightgreen Alice & John
+      participant A
+      participant J
+      end
+      box Another Group very very long description not wrapped
+      participant B
+      end
+      A->>J: Hello John, how are you?
+      J->>A: Great!
+      A->>B: Hello Bob, how are you ?
+      
+
+