From 533830172c9b273c77488d63253c60dd1dac8b27 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 7 Mar 2023 09:38:51 +0530 Subject: [PATCH 01/64] Add UMD back --- .vite/build.ts | 6 ++++++ packages/mermaid/package.json | 11 ++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.vite/build.ts b/.vite/build.ts index 268db3270..e3c947173 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -62,6 +62,12 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) sourcemap: true, entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`, }, + { + name, + format: 'umd', + sourcemap: true, + entryFileNames: `${name}${minify ? '.min' : ''}.js`, + }, ]; if (core) { diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index e40912c29..190d9e783 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,14 +1,16 @@ { "name": "mermaid", - "version": "10.0.2", + "version": "10.0.3-alpha.1", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", + "main": "./dist/mermaid.min.js", "module": "./dist/mermaid.core.mjs", "types": "./dist/mermaid.d.ts", "exports": { ".": { "types": "./dist/mermaid.d.ts", "import": "./dist/mermaid.core.mjs", + "require": "./dist/mermaid.js", "default": "./dist/mermaid.core.mjs" }, "./*": "./*" @@ -106,8 +108,11 @@ "vitepress-plugin-search": "^1.0.4-alpha.19" }, "files": [ - "dist", + "dist/", "README.md" ], - "sideEffects": false + "sideEffects": false, + "publishConfig": { + "access": "public" + } } From c1df62638d1b1a158f74dc6285f03d59dcb2d6f8 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Thu, 9 Mar 2023 21:14:30 +0900 Subject: [PATCH 02/64] class body grammer apply to class stetement only --- .../diagrams/class/parser/classDiagram.jison | 123 ++++++++++-------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 0c9ad2f2a..4e68372f3 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -19,6 +19,8 @@ %x acc_title %x acc_descr %x acc_descr_multiline +%x class +%x class-body %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; @@ -41,35 +43,30 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili \s*(\r?\n)+ return 'NEWLINE'; \s+ /* skip whitespace */ + "classDiagram-v2" return 'CLASS_DIAGRAM'; "classDiagram" return 'CLASS_DIAGRAM'; -[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';} -"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';} -<> return "EOF_IN_STRUCT"; -[{] return "OPEN_IN_STRUCT"; -[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}} -[\n] /* nothing */ -[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";} +"[*]" return 'EDGE_STATE'; -"class" return 'CLASS'; -"cssClass" return 'CSSCLASS'; -"callback" return 'CALLBACK'; -"link" return 'LINK'; -"click" return 'CLICK'; -"note for" return 'NOTE_FOR'; -"note" return 'NOTE'; -"<<" return 'ANNOTATION_START'; -">>" return 'ANNOTATION_END'; -[~] this.begin("generic"); -[~] this.popState(); -[^~]* return "GENERICTYPE"; -["] this.begin("string"); -["] this.popState(); -[^"]* return "STR"; +"class" { this.begin('class'); return 'CLASS';} +\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } +\s+ /* skip whitespace */ +[{] { this.begin("class-body"); return 'STRUCT_START';} +[}] { this.popState(); return 'STRUCT_STOP'; } +<> return "EOF_IN_STRUCT"; +"[*]" { return 'EDGE_STATE';} +[{] return "OPEN_IN_STRUCT"; +[\n] /* nothing */ +[^{}\n]* { return "MEMBER";} -[`] this.begin("bqstring"); -[`] this.popState(); -[^`]+ return "BQUOTE_STR"; +<*>"cssClass" return 'CSSCLASS'; +<*>"callback" return 'CALLBACK'; +<*>"link" return 'LINK'; +<*>"click" return 'CLICK'; +<*>"note for" return 'NOTE_FOR'; +<*>"note" return 'NOTE'; +<*>"<<" return 'ANNOTATION_START'; +<*>">>" return 'ANNOTATION_END'; /* ---interactivity command--- @@ -77,7 +74,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili line was introduced with 'click'. 'href ""' attaches the specified link to the node that was specified by 'click'. */ -"href"[\s]+["] this.begin("href"); +<*>"href"[\s]+["] this.begin("href"); ["] this.popState(); [^"]* return 'HREF'; @@ -89,41 +86,53 @@ the line was introduced with 'click'. arguments to the node that was specified by 'click'. Function arguments are optional: 'call ()' simply executes 'callback_name' without any arguments. */ -"call"[\s]+ this.begin("callback_name"); +<*>"call"[\s]+ this.begin("callback_name"); \([\s]*\) this.popState(); \( this.popState(); this.begin("callback_args"); [^(]* return 'CALLBACK_NAME'; \) this.popState(); [^)]* return 'CALLBACK_ARGS'; -"_self" return 'LINK_TARGET'; -"_blank" return 'LINK_TARGET'; -"_parent" return 'LINK_TARGET'; -"_top" return 'LINK_TARGET'; +[~] this.popState(); +[^~]* return "GENERICTYPE"; +<*>[~] this.begin("generic"); -\s*\<\| return 'EXTENSION'; -\s*\|\> return 'EXTENSION'; -\s*\> return 'DEPENDENCY'; -\s*\< return 'DEPENDENCY'; -\s*\* return 'COMPOSITION'; -\s*o return 'AGGREGATION'; -\s*\(\) return 'LOLLIPOP'; -\-\- return 'LINE'; -\.\. return 'DOTTED_LINE'; -":"{1}[^:\n;]+ return 'LABEL'; -":"{3} return 'STYLE_SEPARATOR'; -\- return 'MINUS'; -"." return 'DOT'; -\+ return 'PLUS'; -\% return 'PCT'; -"=" return 'EQUALS'; -\= return 'EQUALS'; -\w+ return 'ALPHA'; -"[" return 'SQS'; -"]" return 'SQE'; -[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION'; -[0-9]+ return 'NUM'; -[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| +["] this.popState(); +[^"]* return "STR"; +<*>["] this.begin("string"); + +[`] this.popState(); +[^`]+ return "BQUOTE_STR"; +<*>[`] this.begin("bqstring"); + +<*>"_self" return 'LINK_TARGET'; +<*>"_blank" return 'LINK_TARGET'; +<*>"_parent" return 'LINK_TARGET'; +<*>"_top" return 'LINK_TARGET'; + +<*>\s*\<\| return 'EXTENSION'; +<*>\s*\|\> return 'EXTENSION'; +<*>\s*\> return 'DEPENDENCY'; +<*>\s*\< return 'DEPENDENCY'; +<*>\s*\* return 'COMPOSITION'; +<*>\s*o return 'AGGREGATION'; +<*>\s*\(\) return 'LOLLIPOP'; +<*>\-\- return 'LINE'; +<*>\.\. return 'DOTTED_LINE'; +<*>":"{1}[^:\n;]+ return 'LABEL'; +<*>":"{3} return 'STYLE_SEPARATOR'; +<*>\- return 'MINUS'; +<*>"." return 'DOT'; +<*>\+ return 'PLUS'; +<*>\% return 'PCT'; +<*>"=" return 'EQUALS'; +<*>\= return 'EQUALS'; +<*>\w+ return 'ALPHA'; +<*>"[" return 'SQS'; +<*>"]" return 'SQE'; +<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION'; +<*>[0-9]+ return 'NUM'; +<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]| [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]| [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]| @@ -184,9 +193,9 @@ Function arguments are optional: 'call ()' simply executes 'callb [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]| [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]| [\uFFD2-\uFFD7\uFFDA-\uFFDC] - return 'UNICODE_TEXT'; -\s return 'SPACE'; -<> return 'EOF'; + return 'UNICODE_TEXT'; +<*>\s return 'SPACE'; +<*><> return 'EOF'; /lex From 4017bb3c49d26c50ec87e3afcb9bf2b0b8cd1593 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Thu, 9 Mar 2023 17:42:38 +0900 Subject: [PATCH 03/64] Implement namespace parser --- .../mermaid/src/diagrams/class/classDb.ts | 25 +++++++++++++ .../src/diagrams/class/classDiagram.spec.ts | 31 ++++++++++++++++ .../diagrams/class/parser/classDiagram.jison | 36 ++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 48ef7ccbe..851353477 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -392,6 +392,29 @@ const setDirection = (dir: string) => { direction = dir; }; +/** + * Function called by parser when a namespace keyword has been found. + * + * @param id - Id of the namespace to add + * @public + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const addNamespace = function (id: string) { + // TODO: Implement here +}; + +/** + * Function called by parser when a namespace definition has been found. + * + * @param id - Id of the namespace to add + * @param classNames - Ids of the class to add + * @public + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const addClassesToNamespace = function (id: string, classNames: string[]) { + // TODO: Implement here +}; + export default { parseDirective, setAccTitle, @@ -425,4 +448,6 @@ export default { setDiagramTitle, getDiagramTitle, setClassLabel, + addNamespace, + addClassesToNamespace, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 24fbc41ea..11b556179 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -571,6 +571,37 @@ foo() const str = 'classDiagram\n' + 'note "test"\n'; parser.parse(str); }); + + it('should handle "namespace"', function () { + const str = `classDiagram +namespace Namespace1 { class Class1 } +namespace Namespace2 { class Class1 +} +namespace Namespace3 { +class Class1 { +int : test +string : foo +test() +foo() +} +} +namespace Namespace4 { +class Class1 { +int : test +string : foo +test() +foo() +} +class Class2 { +int : test +string : foo +test() +foo() +} +} +`; + parser.parse(str); + }); }); describe('when fetching data from a classDiagram it', function () { diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 4e68372f3..1c0ccdbbe 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -21,6 +21,8 @@ %x acc_descr_multiline %x class %x class-body +%x namespace +%x namespace-body %% \%\%\{ { this.begin('open_directive'); return 'open_directive'; } .*direction\s+TB[^\n]* return 'direction_tb'; @@ -48,9 +50,20 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili "classDiagram" return 'CLASS_DIAGRAM'; "[*]" return 'EDGE_STATE'; -"class" { this.begin('class'); return 'CLASS';} +"namespace" { this.begin('namespace'); return 'NAMESPACE'; } +\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } +\s+ /* skip whitespace */ +[{] { this.begin("namespace-body"); return 'STRUCT_START';} +[}] { this.popState(); return 'STRUCT_STOP'; } +<> return "EOF_IN_STRUCT"; +\s*(\r?\n)+ return 'NEWLINE'; +\s+ /* skip whitespace */ +"[*]" return 'EDGE_STATE'; + +"class" { this.begin('class'); return 'CLASS';} \s*(\r?\n)+ { this.popState(); return 'NEWLINE'; } \s+ /* skip whitespace */ +[}] { this.popState(); this.popState(); return 'STRUCT_STOP';} [{] { this.begin("class-body"); return 'STRUCT_START';} [}] { this.popState(); return 'STRUCT_STOP'; } <> return "EOF_IN_STRUCT"; @@ -264,6 +277,11 @@ classLabel : SQS STR SQE { $$=$2; } ; +namespaceName + : alphaNumToken { $$=$1; } + | alphaNumToken namespaceName { $$=$1+$2; } + ; + className : alphaNumToken { $$=$1; } | classLiteralName { $$=$1; } @@ -275,6 +293,7 @@ className statement : relationStatement { yy.addRelation($1); } | relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); } + | namespaceStatement | classStatement | methodStatement | annotationStatement @@ -288,6 +307,21 @@ statement | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } ; +namespaceStatement + : namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);} + | namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);} + ; + +namespaceIdentifier + : NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);} + ; + +classStatements + : classStatement {$$=[$1]} + | classStatement NEWLINE {$$=[$1]} + | classStatement NEWLINE classStatements {$3.unshift($1); $$=$3} + ; + classStatement : classIdentifier | classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);} From 75502d076e843d3eb932c86027849347df737e08 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 10 Mar 2023 19:37:54 +0900 Subject: [PATCH 04/64] Implement database for namespace data --- .../mermaid/src/diagrams/class/classDb.ts | 46 ++++++++++++++++--- .../src/diagrams/class/classDiagram.spec.ts | 19 ++++++++ .../mermaid/src/diagrams/class/classTypes.ts | 9 ++++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 851353477..18e218244 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -14,7 +14,14 @@ import { setDiagramTitle, getDiagramTitle, } from '../../commonDb'; -import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes'; +import { + ClassRelation, + ClassNode, + ClassNote, + ClassMap, + NamespaceMap, + NamespaceNode, +} from './classTypes'; const MERMAID_DOM_ID_PREFIX = 'classId-'; @@ -22,6 +29,8 @@ let relations: ClassRelation[] = []; let classes: ClassMap = {}; let notes: ClassNote[] = []; let classCounter = 0; +let namespaces: NamespaceMap = {}; +let namespaceCounter = 0; let functions: any[] = []; @@ -100,6 +109,8 @@ export const clear = function () { notes = []; functions = []; functions.push(setupToolTips); + namespaces = {}; + namespaceCounter = 0; commonClear(); }; @@ -393,14 +404,28 @@ const setDirection = (dir: string) => { }; /** - * Function called by parser when a namespace keyword has been found. + * Function called by parser when a namespace definition has been found. * * @param id - Id of the namespace to add * @public */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const addNamespace = function (id: string) { - // TODO: Implement here +export const addNamespace = function (name: string) { + namespaces[name] = { + id: name, + domId: MERMAID_DOM_ID_PREFIX + name + '-' + namespaceCounter, + classes: {}, + children: {}, + }; + + namespaceCounter++; +}; + +const getNamespace = function (name: string): NamespaceNode { + return namespaces[name]; +}; + +const getNamespaces = function (): NamespaceMap { + return namespaces; }; /** @@ -410,9 +435,14 @@ export const addNamespace = function (id: string) { * @param classNames - Ids of the class to add * @public */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const addClassesToNamespace = function (id: string, classNames: string[]) { - // TODO: Implement here + if (namespaces[id] !== undefined) { + classNames.map((className) => { + namespaces[id].classes[className] = classes[className]; + delete classes[className]; + classCounter--; + }); + } }; export default { @@ -450,4 +480,6 @@ export default { setClassLabel, addNamespace, addClassesToNamespace, + getNamespace, + getNamespaces, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 11b556179..48d7ca64d 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1008,6 +1008,25 @@ foo() expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); + + it('should add classes namespaces', function () { + const str = `classDiagram +namespace Namespace1 { +class Class1 { +int : test +string : foo +test() +foo() +} +class Class2 +}`; + parser.parse(str); + + const testNamespace = parser.yy.getNamespace('Namespace1'); + expect(Object.keys(testNamespace.classes).length).toBe(2); + expect(Object.keys(testNamespace.children).length).toBe(0); + expect(testNamespace.classes['Class1'].id).toBe('Class1'); + }); }); describe('when parsing classDiagram with text labels', () => { diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 4cacad3db..cf6f20f0b 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -52,4 +52,13 @@ export type ClassRelation = { lineType: number; }; }; + +export interface NamespaceNode { + id: string; + domId: string; + classes: ClassMap; + children: NamespaceMap; +} + export type ClassMap = Record; +export type NamespaceMap = Record; From 434961b44a5705a9f198047a76712bd97b982f91 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 10 Mar 2023 18:04:05 +0900 Subject: [PATCH 05/64] Implement rendering logic --- .../rendering/classDiagram-v2.spec.js | 14 ++++ .../mermaid/src/diagrams/class/classDb.ts | 20 ++++-- .../src/diagrams/class/classRenderer-v2.ts | 67 ++++++++++++++++++- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 71810cfa4..90ad677ea 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -548,4 +548,18 @@ class C13["With Città foreign language"] ` ); }); + it('should add classes namespaces', function () { + imgSnapshotTest( + ` + classDiagram + namespace Namespace1 { + class C1 + class C2 + } + C1 --> C2 + class C3 + class C4 + ` + ); + }); }); diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 18e218244..6468b5e8c 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -245,7 +245,11 @@ const setTooltip = function (ids: string, tooltip?: string) { } }); }; -export const getTooltip = function (id: string) { +export const getTooltip = function (id: string, namespace?: string) { + if (namespace) { + return namespaces[namespace].classes[id].tooltip; + } + return classes[id].tooltip; }; /** @@ -409,13 +413,17 @@ const setDirection = (dir: string) => { * @param id - Id of the namespace to add * @public */ -export const addNamespace = function (name: string) { - namespaces[name] = { - id: name, - domId: MERMAID_DOM_ID_PREFIX + name + '-' + namespaceCounter, +export const addNamespace = function (id: string) { + if (namespaces[id] !== undefined) { + return; + } + + namespaces[id] = { + id: id, classes: {}, children: {}, - }; + domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter, + } as NamespaceNode; namespaceCounter++; }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index e308990c6..e9b221141 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -8,7 +8,7 @@ import utils from '../../utils'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes'; +import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes'; const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig()); @@ -19,6 +19,58 @@ let conf = { curve: undefined, }; +interface RectParameters { + id: string; + shape: 'rect'; + labelStyle: string; + domId: string; + labelText: string; + padding: number | undefined; + style?: string; +} + +/** + * Function that adds the vertices found during parsing to the graph to be rendered. + * + * @param namespaces - Object containing the vertices. + * @param g - The graph that is to be drawn. + * @param _id - id of the graph + * @param diagObj - The diagram object + */ +export const addNamespaces = function ( + namespaces: NamespaceMap, + g: graphlib.Graph, + _id: string, + diagObj: any +) { + const keys = Object.keys(namespaces); + log.info('keys:', keys); + log.info(namespaces); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function (id) { + const vertex = namespaces[id]; + + // parent node must be one of [rect, roundedWithTitle, noteGroup, divider] + const shape = 'rect'; + + const node: RectParameters = { + shape: shape, + id: vertex.id, + domId: vertex.domId, + labelText: sanitizeText(vertex.id), + labelStyle: '', + // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release + padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, + }; + + g.setNode(vertex.id, node); + addClasses(vertex.classes, g, _id, diagObj, vertex.id); + + log.info('setNode', node); + }); +}; + /** * Function that adds the vertices found during parsing to the graph to be rendered. * @@ -31,7 +83,8 @@ export const addClasses = function ( classes: ClassMap, g: graphlib.Graph, _id: string, - diagObj: any + diagObj: any, + parent?: string ) { const keys = Object.keys(classes); log.info('keys:', keys); @@ -55,6 +108,7 @@ export const addClasses = function ( const vertexText = vertex.label ?? vertex.id; const radius = 0; const shape = 'class_box'; + // Add the node const node = { labelStyle: styles.labelStyle, @@ -67,7 +121,7 @@ export const addClasses = function ( style: styles.style, id: vertex.id, domId: vertex.domId, - tooltip: diagObj.db.getTooltip(vertex.id) || '', + tooltip: diagObj.db.getTooltip(vertex.id, parent) || '', haveCallback: vertex.haveCallback, link: vertex.link, width: vertex.type === 'group' ? 500 : undefined, @@ -76,6 +130,11 @@ export const addClasses = function ( padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, }; g.setNode(vertex.id, node); + + if (parent) { + g.setParent(vertex.id, parent); + } + log.info('setNode', node); }); }; @@ -275,10 +334,12 @@ export const draw = function (text: string, id: string, _version: string, diagOb }); // Fetch the vertices/nodes and edges/links from the parsed graph definition + const namespaces: NamespaceMap = diagObj.db.getNamespaces(); const classes: ClassMap = diagObj.db.getClasses(); const relations: ClassRelation[] = diagObj.db.getRelations(); const notes: ClassNote[] = diagObj.db.getNotes(); log.info(relations); + addNamespaces(namespaces, g, id, diagObj); addClasses(classes, g, id, diagObj); addRelations(relations, g); addNotes(notes, g, relations.length + 1, classes); From b51b2c7b04fab4135044b984dcb94a4a4edd6c41 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 17 Mar 2023 14:21:31 +0900 Subject: [PATCH 06/64] Add namespace sample to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index d42e2f7e1..d8e06b62f 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,13 @@ class Class10 { int id size() } +namespace Namespace01 { + class Class11 + class Class12 { + int id + size() + } +} ``` ```mermaid @@ -184,6 +191,13 @@ class Class10 { int id size() } +namespace Namespace01 { + class Class11 + class Class12 { + int id + size() + } +} ``` ### State diagram [docs - live editor] From b725b69fda5641904a5ec89ef75533093439a874 Mon Sep 17 00:00:00 2001 From: Kazuki Tsunemi Date: Fri, 17 Mar 2023 14:47:32 +0900 Subject: [PATCH 07/64] Add package of class diagrams to doc --- docs/syntax/classDiagram.md | 28 +++++++++++++++++++ .../mermaid/src/docs/syntax/classDiagram.md | 17 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 69144ef39..bc44a4c48 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -421,6 +421,34 @@ And `Link` can be one of: | -- | Solid | | .. | Dashed | +## Define Namespace + +A namespace groups classes. + +Code: + +```mermaid-example +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + +```mermaid +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + ## Cardinality / Multiplicity on relations Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies. diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 9d3766590..588aff874 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -277,6 +277,23 @@ And `Link` can be one of: | -- | Solid | | .. | Dashed | +## Define Namespace + +A namespace groups classes. + +Code: + +```mermaid-example +classDiagram +namespace BaseShapes { + class Triangle + class Rectangle { + double width + double height + } +} +``` + ## Cardinality / Multiplicity on relations Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies. From e357bbee4287eb303db00ed31738d6ef2ecc809f Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Thu, 30 Mar 2023 11:04:19 -0700 Subject: [PATCH 08/64] Refactor to consolidate shared svgDraw components --- packages/mermaid/src/diagrams/c4/svgDraw.js | 46 ++----- .../mermaid/src/diagrams/class/svgDraw.js | 2 +- .../src/diagrams/common/svgDrawCommon.js | 122 ++++++++++++++++++ .../mermaid/src/diagrams/mindmap/svgDraw.js | 4 + .../src/diagrams/sequence/sequenceRenderer.ts | 7 +- .../mermaid/src/diagrams/sequence/svgDraw.js | 113 +++------------- .../src/diagrams/sequence/svgDraw.spec.js | 12 -- .../src/diagrams/user-journey/svgDraw.js | 79 +----------- 8 files changed, 165 insertions(+), 220 deletions(-) create mode 100644 packages/mermaid/src/diagrams/common/svgDrawCommon.js diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index 690dd26ad..ce465cad3 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -1,28 +1,9 @@ import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.attrs !== 'undefined' && rectData.attrs !== null) { - for (let attrKey in rectData.attrs) { - rectElem.attr(attrKey, rectData.attrs[attrKey]); - } - } - - if (rectData.class !== 'undefined') { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; export const drawImage = function (elem, width, height, x, y, link) { @@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) { // // draw rect of c4Shape - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); + switch (c4Shape.typeC4Shape.text) { case 'person': case 'external_person': @@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) { .append('path') .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; + export const insertArrowEnd = function (elem) { elem .append('defs') @@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) { .append('path') .attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; + /** * Setup node number. The result is appended to the svg. * @@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) { .attr('r', 6); // .style("fill", '#f00'); }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getNoteRect = function () { - return { - x: 0, - y: 0, - fill: '#EDF2AE', - stroke: '#666', - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; -}; - const getC4ShapeFont = (cnf, typeC4Shape) => { return { fontFamily: cnf[typeC4Shape + 'FontFamily'], @@ -714,6 +686,4 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getNoteRect, - sanitizeUrl, // TODO why is this exported? }; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index cc6909280..bc6d0ab11 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -440,7 +440,6 @@ const buildLegacyDisplay = function (text) { } const parameters = text.substring(methodStart + 1, methodEnd); - const classifier = text.substring(methodEnd + 1, 1); cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2)); displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; @@ -462,6 +461,7 @@ const buildLegacyDisplay = function (text) { cssStyle, }; }; + /** * Adds a for a member in a diagram * diff --git a/packages/mermaid/src/diagrams/common/svgDrawCommon.js b/packages/mermaid/src/diagrams/common/svgDrawCommon.js new file mode 100644 index 000000000..e054f53b6 --- /dev/null +++ b/packages/mermaid/src/diagrams/common/svgDrawCommon.js @@ -0,0 +1,122 @@ +import { sanitizeUrl } from '@braintree/sanitize-url'; + +export const drawRect = function (elem, rectData) { + const rectElem = elem.append('rect'); + rectElem.attr('x', rectData.x); + rectElem.attr('y', rectData.y); + rectElem.attr('fill', rectData.fill); + rectElem.attr('stroke', rectData.stroke); + rectElem.attr('width', rectData.width); + rectElem.attr('height', rectData.height); + rectElem.attr('rx', rectData.rx); + rectElem.attr('ry', rectData.ry); + + if (rectData.attrs !== 'undefined' && rectData.attrs !== null) { + for (let attrKey in rectData.attrs) { + rectElem.attr(attrKey, rectData.attrs[attrKey]); + } + } + + if (rectData.class !== 'undefined') { + rectElem.attr('class', rectData.class); + } + + return rectElem; +}; + +/** + * Draws a background rectangle + * + * @param {any} elem Diagram (reference for bounds) + * @param {any} bounds Shape of the rectangle + */ +export const drawBackgroundRect = function (elem, bounds) { + const rectElem = drawRect(elem, { + x: bounds.startx, + y: bounds.starty, + width: bounds.stopx - bounds.startx, + height: bounds.stopy - bounds.starty, + fill: bounds.fill, + stroke: bounds.stroke, + class: 'rect', + }); + rectElem.lower(); +}; + +export const drawText = function (elem, textData) { + // Remove and ignore br:s + const nText = textData.text.replace(//gi, ' '); + + const textElem = elem.append('text'); + textElem.attr('x', textData.x); + textElem.attr('y', textData.y); + textElem.attr('class', 'legend'); + + textElem.style('text-anchor', textData.anchor); + + if (textData.class !== undefined) { + textElem.attr('class', textData.class); + } + + const span = textElem.append('tspan'); + span.attr('x', textData.x + textData.textMargin * 2); + span.text(nText); + + return textElem; +}; + +export const drawImage = function (elem, x, y, link) { + const imageElem = elem.append('image'); + imageElem.attr('x', x); + imageElem.attr('y', y); + var sanitizedLink = sanitizeUrl(link); + imageElem.attr('xlink:href', sanitizedLink); +}; + +export const drawEmbeddedImage = function (elem, x, y, link) { + const imageElem = elem.append('use'); + imageElem.attr('x', x); + imageElem.attr('y', y); + var sanitizedLink = sanitizeUrl(link); + imageElem.attr('xlink:href', '#' + sanitizedLink); +}; + +export const getNoteRect = function () { + return { + x: 0, + y: 0, + width: 100, + height: 100, + fill: '#EDF2AE', + stroke: '#666', + anchor: 'start', + rx: 0, + ry: 0, + }; +}; + +export const getTextObj = function () { + return { + x: 0, + y: 0, + width: 100, + height: 100, + fill: undefined, + anchor: undefined, + 'text-anchor': 'start', + style: '#666', + textMargin: 0, + rx: 0, + ry: 0, + tspan: true, + valign: undefined, + }; +}; + +export default { + drawRect, + drawImage, + drawText, + getNoteRect, + getTextObj, +}; diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js index 2b1aa021e..44172f222 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js @@ -69,6 +69,7 @@ const defaultBkg = function (elem, node, section) { .attr('x2', node.width) .attr('y2', node.height); }; + const rectBkg = function (elem, node) { elem .append('rect') @@ -77,6 +78,7 @@ const rectBkg = function (elem, node) { .attr('height', node.height) .attr('width', node.width); }; + const cloudBkg = function (elem, node) { const w = node.width; const h = node.height; @@ -107,6 +109,7 @@ const cloudBkg = function (elem, node) { H0 V0 Z` ); }; + const bangBkg = function (elem, node) { const w = node.width; const h = node.height; @@ -138,6 +141,7 @@ const bangBkg = function (elem, node) { H0 V0 Z` ); }; + const circleBkg = function (elem, node) { elem .append('circle') diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index acee7bbe5..f45de2e49 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -3,6 +3,7 @@ import { select, selectAll } from 'd3'; import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw'; import { log } from '../../logger'; import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; @@ -225,7 +226,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) { bounds.bumpVerticalPos(conf.boxMargin); noteModel.height = conf.boxMargin; noteModel.starty = bounds.getVerticalPos(); - const rect = svgDraw.getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = noteModel.startx; rect.y = noteModel.starty; rect.width = noteModel.width || conf.width; @@ -233,7 +234,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) { const g = elem.append('g'); const rectElem = svgDraw.drawRect(g, rect); - const textObj = svgDraw.getTextObj(); + const textObj = svgDrawCommon.getTextObj(); textObj.x = noteModel.startx; textObj.y = noteModel.starty; textObj.width = rect.width; @@ -347,7 +348,7 @@ function boundMessage(_diagram, msgModel): number { const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) { const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel; const textDims = utils.calculateTextDimensions(message, messageFont(conf)); - const textObj = svgDraw.getTextObj(); + const textObj = svgDrawCommon.getTextObj(); textObj.x = startx; textObj.y = starty + 10; textObj.width = stopx - startx; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index be34daf4b..668cecb36 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,33 +1,13 @@ import common from '../common/common'; +import * as svgDrawCommon from '../common/svgDrawCommon'; import { addFunction } from '../../interactionDb'; import { parseFontSize } from '../../utils'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.class !== undefined) { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; -// const sanitizeUrl = function (s) { -// return s -// .replace(/&/g, '&') -// .replace(/ { addFunction(() => { const arr = document.querySelectorAll(id); @@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => { }); }); }; + export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) { if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) { return { height: 0, width: 0 }; @@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe return { height: rectData.height + linkY, width: menuWidth }; }; -export const drawImage = function (elem, x, y, link) { - const imageElem = elem.append('image'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', sanitizedLink); -}; - -export const drawEmbeddedImage = function (elem, x, y, link) { - const imageElem = elem.append('use'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', '#' + sanitizedLink); -}; - export const popupMenu = function (popid) { return ( "var pu = document.getElementById('" + @@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) { pu.style.display = 'none'; } }; + export const drawText = function (elem, textData) { - let prevTextHeight = 0, - textHeight = 0; + let prevTextHeight = 0; + let textHeight = 0; const lines = textData.text.split(common.lineBreakRegex); const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize); @@ -188,6 +154,7 @@ export const drawText = function (elem, textData) { break; } } + if ( textData.anchor !== undefined && textData.textMargin !== undefined && @@ -217,6 +184,7 @@ export const drawText = function (elem, textData) { break; } } + for (let [i, line] of lines.entries()) { if ( textData.textMargin !== undefined && @@ -371,7 +339,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { } } - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); var cssclass = 'actor'; if (actor.properties != null && actor.properties['class']) { cssclass = actor.properties['class']; @@ -391,9 +359,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { if (actor.properties != null && actor.properties['icon']) { const iconSrc = actor.properties['icon'].trim(); if (iconSrc.charAt(0) === '@') { - drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); + svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1)); } else { - drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); + svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc); } } @@ -438,7 +406,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { const actElem = elem.append('g'); actElem.attr('class', 'actor-man'); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = actor.x; rect.y = actor.y; rect.fill = '#eaeaea'; @@ -447,7 +415,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) { rect.class = 'actor'; rect.rx = 3; rect.ry = 3; - // drawRect(actElem, rect); actElem .append('line') @@ -532,6 +499,7 @@ export const drawBox = function (elem, box, conf) { export const anchorElement = function (elem) { return elem.append('g'); }; + /** * Draws an activation in the diagram * @@ -542,7 +510,7 @@ export const anchorElement = function (elem) { * @param {any} actorActivations - Number of activations on the actor. */ export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) { - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); const g = bounds.anchored; rect.x = bounds.startx; rect.y = bounds.starty; @@ -594,7 +562,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { }); } - let txt = getTextObj(); + let txt = svgDrawCommon.getTextObj(); txt.text = labelText; txt.x = loopModel.startx; txt.y = loopModel.starty; @@ -610,7 +578,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { txt.class = 'labelText'; drawLabel(g, txt); - txt = getTextObj(); + txt = svgDrawCommon.getTextObj(); txt.text = loopModel.title; txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2; txt.y = loopModel.starty + boxMargin + boxTextMargin; @@ -661,16 +629,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { * @param {any} bounds Shape of the rectangle */ export const drawBackgroundRect = function (elem, bounds) { - const rectElem = drawRect(elem, { - x: bounds.startx, - y: bounds.starty, - width: bounds.stopx - bounds.startx, - height: bounds.stopy - bounds.starty, - fill: bounds.fill, - stroke: bounds.stroke, - class: 'rect', - }); - rectElem.lower(); + svgDrawCommon.drawBackgroundRect(elem, bounds); }; export const insertDatabaseIcon = function (elem) { @@ -737,6 +696,7 @@ export const insertArrowHead = function (elem) { .append('path') .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead }; + /** * Setup arrow head and define the marker. The result is appended to the svg. * @@ -755,6 +715,7 @@ export const insertArrowFilledHead = function (elem) { .append('path') .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z'); }; + /** * Setup node number. The result is appended to the svg. * @@ -776,6 +737,7 @@ export const insertSequenceNumber = function (elem) { .attr('r', 6); // .style("fill", '#f00'); }; + /** * Setup cross head and define the marker. The result is appended to the svg. * @@ -802,37 +764,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - anchor: undefined, - style: '#666', - width: undefined, - height: undefined, - textMargin: 0, - rx: 0, - ry: 0, - tspan: true, - valign: undefined, - }; -}; - -export const getNoteRect = function () { - return { - x: 0, - y: 0, - fill: '#EDF2AE', - stroke: '#666', - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; -}; - const _drawTextCandidateFunc = (function () { /** * @param {any} content @@ -1062,8 +993,6 @@ export default { drawActor, drawBox, drawPopup, - drawImage, - drawEmbeddedImage, anchorElement, drawActivation, drawLoop, @@ -1075,8 +1004,6 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getTextObj, - getNoteRect, popupMenu, popdownMenu, fixLifeLineHeights, diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index ed60285ed..648ee58f8 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -174,16 +174,4 @@ describe('svgDraw', function () { expect(rect.lower).toHaveBeenCalled(); }); }); - describe('sanitizeUrl', function () { - it('should sanitize malicious urls', function () { - const maliciousStr = 'javascript:script:alert(1)'; - const result = svgDraw.sanitizeUrl(maliciousStr); - expect(result).not.toContain('javascript:alert(1)'); - }); - it('should not sanitize non dangerous urls', function () { - const maliciousStr = 'javajavascript:script:alert(1)'; - const result = svgDraw.sanitizeUrl(maliciousStr); - expect(result).not.toContain('javascript:alert(1)'); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/user-journey/svgDraw.js b/packages/mermaid/src/diagrams/user-journey/svgDraw.js index f6dbe71e1..108f4b2f9 100644 --- a/packages/mermaid/src/diagrams/user-journey/svgDraw.js +++ b/packages/mermaid/src/diagrams/user-journey/svgDraw.js @@ -1,21 +1,8 @@ import { arc as d3arc } from 'd3'; +import * as svgDrawCommon from '../common/svgDrawCommon'; export const drawRect = function (elem, rectData) { - const rectElem = elem.append('rect'); - rectElem.attr('x', rectData.x); - rectElem.attr('y', rectData.y); - rectElem.attr('fill', rectData.fill); - rectElem.attr('stroke', rectData.stroke); - rectElem.attr('width', rectData.width); - rectElem.attr('height', rectData.height); - rectElem.attr('rx', rectData.rx); - rectElem.attr('ry', rectData.ry); - - if (rectData.class !== undefined) { - rectElem.attr('class', rectData.class); - } - - return rectElem; + return svgDrawCommon.drawRect(elem, rectData); }; export const drawFace = function (element, faceData) { @@ -128,25 +115,7 @@ export const drawCircle = function (element, circleData) { }; export const drawText = function (elem, textData) { - // Remove and ignore br:s - const nText = textData.text.replace(//gi, ' '); - - const textElem = elem.append('text'); - textElem.attr('x', textData.x); - textElem.attr('y', textData.y); - textElem.attr('class', 'legend'); - - textElem.style('text-anchor', textData.anchor); - - if (textData.class !== undefined) { - textElem.attr('class', textData.class); - } - - const span = textElem.append('tspan'); - span.attr('x', textData.x + textData.textMargin * 2); - span.text(nText); - - return textElem; + return svgDrawCommon.drawText(elem, textData); }; export const drawLabel = function (elem, txtObject) { @@ -192,7 +161,7 @@ export const drawLabel = function (elem, txtObject) { export const drawSection = function (elem, section, conf) { const g = elem.append('g'); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = section.x; rect.y = section.y; rect.fill = section.fill; @@ -249,7 +218,7 @@ export const drawTask = function (elem, task, conf) { score: task.score, }); - const rect = getNoteRect(); + const rect = svgDrawCommon.getNoteRect(); rect.x = task.x; rect.y = task.y; rect.fill = task.fill; @@ -298,41 +267,7 @@ export const drawTask = function (elem, task, conf) { * @param {any} bounds The bounds of the drawing */ export const drawBackgroundRect = function (elem, bounds) { - const rectElem = drawRect(elem, { - x: bounds.startx, - y: bounds.starty, - width: bounds.stopx - bounds.startx, - height: bounds.stopy - bounds.starty, - fill: bounds.fill, - class: 'rect', - }); - rectElem.lower(); -}; - -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - 'text-anchor': 'start', - width: 100, - height: 100, - textMargin: 0, - rx: 0, - ry: 0, - }; -}; - -export const getNoteRect = function () { - return { - x: 0, - y: 0, - width: 100, - anchor: 'start', - height: 100, - rx: 0, - ry: 0, - }; + svgDrawCommon.drawBackgroundRect(elem, bounds); }; const _drawTextCandidateFunc = (function () { @@ -475,7 +410,5 @@ export default { drawLabel, drawTask, drawBackgroundRect, - getTextObj, - getNoteRect, initGraphics, }; From 5693f6360333811a0ca8085e698bea8203946704 Mon Sep 17 00:00:00 2001 From: Lishid Date: Tue, 4 Apr 2023 11:22:19 -0400 Subject: [PATCH 09/64] Fix git graph css bracket leak --- packages/mermaid/src/diagrams/git/styles.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/git/styles.js b/packages/mermaid/src/diagrams/git/styles.js index 741760235..1836e9dff 100644 --- a/packages/mermaid/src/diagrams/git/styles.js +++ b/packages/mermaid/src/diagrams/git/styles.js @@ -56,7 +56,6 @@ const getStyles = (options) => font-size: 18px; fill: ${options.textColor}; } - } `; export default getStyles; From 24c9506935d353eaea8f234c9f8ab910b284bbfe Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 5 Apr 2023 22:07:46 +0530 Subject: [PATCH 10/64] fix version --- packages/mermaid/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index fc789e292..5f7978844 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "10.0.3-alpha.1", + "version": "10.1.0", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", "main": "./dist/mermaid.min.js", From 19363965adae87e8eb37eb835fa38a8453f1f1c7 Mon Sep 17 00:00:00 2001 From: Will-Low <26700668+Will-Low@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:08:48 -0700 Subject: [PATCH 11/64] Updating documentation on notes for classes I was confused by the documentation on notes for a specific class. Updated the wording slightly and added an example for clarity. --- .../mermaid/src/docs/syntax/classDiagram.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 9d3766590..f824b0bfc 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -403,10 +403,26 @@ click className href "url" "tooltip" ## Notes -It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"` +It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for "line1\nline2"`. ### Examples +```mmd +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + +```mermaid +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + _URL Link:_ ```mmd From e3ca0031273443d12d1ac2008978bacd84d4932b Mon Sep 17 00:00:00 2001 From: Will-Low <26700668+Will-Low@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:59:11 -0700 Subject: [PATCH 12/64] Rebuilding docs --- docs/syntax/classDiagram.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 69144ef39..9c8f8aadb 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -604,10 +604,42 @@ You would define these actions on a separate line after all classes have been de ## Notes -It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"` +It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for "line1\nline2"`. ### Examples +```mermaid-example +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + +```mermaid +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + +```mermaid-example +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + +```mermaid +classDiagram + note "This is a general note" + note for MyClass "This is a note for a class" + class MyClass{ + } +``` + _URL Link:_ ```mermaid-example From 8aa51cf3e7272e41ddeabed05d3154f99c8d4155 Mon Sep 17 00:00:00 2001 From: Bishop Clark Date: Thu, 13 Apr 2023 16:59:13 -0700 Subject: [PATCH 13/64] Update bug_report.yml correct adjective order --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1d1724a5e..1b84bfd45 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -50,7 +50,7 @@ body: attributes: label: Setup description: |- - Please fill out the below info. + Please fill out the info below. Note that you only need to fill out the relevant section value: |- - Mermaid version: From ac63788a9c7b27e23dc67517d56d0ce8cff7f386 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 11 Apr 2023 06:37:53 -0700 Subject: [PATCH 14/64] organized and fixed tests --- .../src/diagrams/class/classDiagram.spec.ts | 1521 ++++++++--------- .../diagrams/class/parser/classDiagram.jison | 2 +- 2 files changed, 672 insertions(+), 851 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 6dbf9c081..060ba6911 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -4,18 +4,13 @@ import classDb from './classDb.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; -describe('class diagram, ', function () { - describe('when parsing a class diagram', function () { +describe('given a class diagram, ', function () { + describe('when parsing class definition', function () { beforeEach(function () { parser.yy = classDb; }); - it('should handle backquoted class names', function () { - const str = 'classDiagram\n' + 'class `Car`'; - - parser.parse(str); - }); - it.skip('should handle a leading newline axa', function () { + it.skip('should handle a leading newline', function () { const str = '\nclassDiagram\n' + 'class Car'; try { @@ -25,161 +20,20 @@ describe('class diagram, ', function () { expect(true).toBe(false); } }); - it('should handle relation definitions', function () { - const str = - 'classDiagram\n' + - 'Class01 <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class1'; + + it('should handle backquoted class names', function () { + const str = 'classDiagram\n' + 'class `Car`'; parser.parse(str); }); - it('should handle backquoted relation definitions', function () { - const str = - 'classDiagram\n' + - '`Class01` <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class1'; + it('should handle class names with underscore', function () { + const str = 'classDiagram\n' + 'class `A_Car`'; parser.parse(str); }); - it('should handle relation definition of different types and directions', function () { - const str = - 'classDiagram\n' + - 'Class11 <|.. Class12\n' + - 'Class13 --> Class14\n' + - 'Class15 ..> Class16\n' + - 'Class17 ..|> Class18\n' + - 'Class19 <--* Class20'; - - parser.parse(str); - }); - - it('should handle cardinality and labels', function () { - const str = - 'classDiagram\n' + - 'Class01 "1" *-- "many" Class02 : contains\n' + - 'Class03 o-- Class04 : aggregation\n' + - 'Class05 --> "1" Class06'; - - parser.parse(str); - }); - - it('should handle visibility for methods and members', function () { - const str = - 'classDiagram\n' + - 'class TestClass\n' + - 'TestClass : -int privateMember\n' + - 'TestClass : +int publicMember\n' + - 'TestClass : #int protectedMember\n' + - 'TestClass : -privateMethod()\n' + - 'TestClass : +publicMethod()\n' + - 'TestClass : #protectedMethod()\n'; - - parser.parse(str); - }); - - it('should handle generic class', function () { - const str = - 'classDiagram\n' + - 'class Car~T~\n' + - 'Driver -- Car : drives >\n' + - 'Car *-- Wheel : have 4 >\n' + - 'Car -- Person : < owns'; - - parser.parse(str); - }); - - it('should handle generic class with a literal name', function () { - const str = - 'classDiagram\n' + - 'class `Car`~T~\n' + - 'Driver -- `Car` : drives >\n' + - '`Car` *-- Wheel : have 4 >\n' + - '`Car` -- Person : < owns'; - - parser.parse(str); - }); - - it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Dummy_Class {\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - let testPassed = false; - try { - parser.parse(str); - } catch (error) { - testPassed = true; - } - expect(testPassed).toBe(true); - }); - - it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Dummy_Class {\n'; - let testPassed = false; - try { - parser.parse(str); - } catch (error) { - testPassed = true; - } - expect(testPassed).toBe(true); - }); - - it('should handle generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - - parser.parse(str); - }); - - it('should handle generic class with brackets and a literal name', function () { - const str = - 'classDiagram\n' + - 'class `Dummy_Class`~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - - parser.parse(str); - }); - - it('should handle class definitions', function () { + it('should handle basic class definitions', function () { const str = 'classDiagram\n' + 'class Car\n' + @@ -190,35 +44,23 @@ describe('class diagram, ', function () { parser.parse(str); }); - it('should handle cssClass shorthand with members', () => { - parser.parse(`classDiagram-v2 - class Class10:::exClass2 { - int[] id - List~int~ ids - test(List~int~ ids) List~bool~ - testArray() bool[] - }`); + it('should handle member definitions in brackets', function () { + const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}'; - expect(classDb.getClass('Class10')).toMatchInlineSnapshot(` - { - "annotations": [], - "cssClasses": [ - "exClass2", - ], - "domId": "classId-Class10-27", - "id": "Class10", - "label": "Class10", - "members": [ - "int[] id", - "List~int~ ids", - ], - "methods": [ - "test(List~int~ ids) List~bool~", - "testArray() bool[]", - ], - "type": "", - } - `); + parser.parse(str); + }); + + it('should handle method declaration in brackets', function () { + const str = 'classDiagram\n' + 'class Car{\n' + '+size()\n' + '}'; + + parser.parse(str); + }); + + it('should handle properties in brackets, and some outside', function () { + const str = + 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}\n' + 'Car : +ArrayList size()\n'; + + parser.parse(str); }); it('should handle method statements', function () { @@ -232,6 +74,20 @@ describe('class diagram, ', function () { parser.parse(str); }); + it('should handle visibility for methods and members', function () { + const str = + 'classDiagram\n' + + 'class actual\n' + + 'actual : -int privateMember\n' + + 'actual : +int publicMember\n' + + 'actual : #int protectedMember\n' + + 'actual : -privateMethod()\n' + + 'actual : +publicMethod()\n' + + 'actual : #protectedMethod()\n'; + + parser.parse(str); + }); + it('should handle parsing of method statements grouped by brackets', function () { const str = 'classDiagram\n' + @@ -311,467 +167,6 @@ describe('class diagram, ', function () { parser.parse(str); }); - it('should handle a comment', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}'; - - parser.parse(str); - }); - - it('should handle comments at the start', function () { - const str = `%% Comment - classDiagram - class Class1 { - int : test - string : foo - test() - foo() - }`; - parser.parse(str); - }); - - it('should handle comments at the end', function () { - const str = `classDiagram -class Class1 { -int : test -string : foo -test() -foo() - -} -%% Comment -`; - - parser.parse(str); - }); - - it('should handle comments at the end no trailing newline', function () { - const str = `classDiagram -class Class1 { -int : test -string : foo -test() -foo() -} -%% Comment`; - - parser.parse(str); - }); - - it('should handle a comment with multiple line feeds', function () { - const str = `classDiagram - - -%% Comment - -class Class1 { -int : test -string : foo -test() -foo() -}`; - - parser.parse(str); - }); - - it('should handle a comment with mermaid class diagram code in them', function () { - const str = `classDiagram -%% Comment Class01 <|-- Class02 -class Class1 { -int : test -string : foo -test() -foo() -}`; - - parser.parse(str); - }); - - it('should handle a comment inside brackets', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}'; - - parser.parse(str); - }); - - it('should handle click statement with link', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'link Class01 "google.com" '; - - parser.parse(str); - }); - - it('should handle click statement with click and href link', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'click Class01 href "google.com" '; - - parser.parse(str); - }); - - it('should handle click statement with link and tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'link Class01 "google.com" "A Tooltip" '; - - parser.parse(str); - }); - - it('should handle click statement with click and href link and tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'click Class01 href "google.com" "A Tooltip" '; - - parser.parse(str); - }); - - it('should handle click statement with callback', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'callback Class01 "functionCall" '; - - parser.parse(str); - }); - - it('should handle click statement with click and call callback', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'click Class01 call functionCall() '; - - parser.parse(str); - }); - - it('should handle click statement with callback and tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'callback Class01 "functionCall" "A Tooltip" '; - - parser.parse(str); - }); - - it('should handle click statement with click and call callback and tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '%% Comment Class01 <|-- Class02\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}\n' + - 'click Class01 call functionCall() "A Tooltip" '; - - parser.parse(str); - }); - - it('should handle dashed relation definition of different types and directions', function () { - const str = - 'classDiagram\n' + - 'Class11 <|.. Class12\n' + - 'Class13 <.. Class14\n' + - 'Class15 ..|> Class16\n' + - 'Class17 ..> Class18\n' + - 'Class19 .. Class20'; - parser.parse(str); - }); - - it('should handle generic types in members', function () { - const str = - 'classDiagram\n' + - 'class Car~T~\n' + - 'Car : -List~Wheel~ wheels\n' + - 'Car : +setWheels(List~Wheel~ wheels)\n' + - 'Car : +getWheels() List~Wheel~'; - - parser.parse(str); - }); - - it('should handle generic types in members in class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Car {\n' + - 'List~Wheel~ wheels\n' + - 'setWheels(List~Wheel~ wheels)\n' + - '+getWheels() List~Wheel~\n' + - '}'; - - parser.parse(str); - }); - - it('should handle "note for"', function () { - const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n'; - parser.parse(str); - }); - - it('should handle "note"', function () { - const str = 'classDiagram\n' + 'note "test"\n'; - parser.parse(str); - }); - }); - - describe('when fetching data from a classDiagram it', function () { - beforeEach(function () { - parser.yy = classDb; - parser.yy.clear(); - }); - it('should handle relation definitions EXTENSION', function () { - const str = 'classDiagram\n' + 'Class01 <|-- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle accTitle and accDescr', function () { - const str = `classDiagram - accTitle: My Title - accDescr: My Description - - Class01 <|-- Class02 - `; - - parser.parse(str); - expect(parser.yy.getAccTitle()).toBe('My Title'); - expect(parser.yy.getAccDescription()).toBe('My Description'); - }); - it('should handle accTitle and multiline accDescr', function () { - const str = `classDiagram - accTitle: My Title - accDescr { - This is mu multi - line description - } - - Class01 <|-- Class02 - `; - - parser.parse(str); - expect(parser.yy.getAccTitle()).toBe('My Title'); - expect(parser.yy.getAccDescription()).toBe('This is mu multi\nline description'); - }); - - it('should handle relation definitions AGGREGATION and dotted line', function () { - const str = 'classDiagram\n' + 'Class01 o.. Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); - }); - - it('should handle relation definitions COMPOSITION on both sides', function () { - const str = 'classDiagram\n' + 'Class01 *--* Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION); - expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle relation definitions no types', function () { - const str = 'classDiagram\n' + 'Class01 -- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe('none'); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle relation definitions with type only on right side', function () { - const str = 'classDiagram\n' + 'Class01 --|> Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe('none'); - expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle multiple classes and relation definitions', function () { - const str = - 'classDiagram\n' + - 'Class01 <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class10'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class10').id).toBe('Class10'); - - expect(relations.length).toBe(5); - - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - expect(relations[3].relation.type1).toBe('none'); - expect(relations[3].relation.type2).toBe('none'); - expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); - }); - - it('should handle generic class with relation definitions', function () { - const str = 'classDiagram\n' + 'Class01~T~ <|-- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class01').id).toBe('Class01'); - expect(parser.yy.getClass('Class01').type).toBe('T'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle class annotations', function () { - const str = 'classDiagram\n' + 'class Class1\n' + '<> Class1'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(0); - expect(testClass.methods.length).toBe(0); - expect(testClass.annotations[0]).toBe('interface'); - }); - - it('should handle class annotations with members and methods', function () { - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : int test\n' + - 'Class1 : test()\n' + - '<> Class1'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(1); - expect(testClass.methods.length).toBe(1); - expect(testClass.annotations[0]).toBe('interface'); - }); - - it('should handle class annotations in brackets', function () { - const str = 'classDiagram\n' + 'class Class1 {\n' + '<>\n' + '}'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(0); - expect(testClass.methods.length).toBe(0); - expect(testClass.annotations[0]).toBe('interface'); - }); - - it('should handle class annotations in brackets with members and methods', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - '<>\n' + - 'int : test\n' + - 'test()\n' + - '}'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(1); - expect(testClass.members.length).toBe(1); - expect(testClass.methods.length).toBe(1); - expect(testClass.annotations[0]).toBe('interface'); - }); - it('should add bracket members in right order', function () { const str = 'classDiagram\n' + @@ -783,206 +178,13 @@ foo() '}'; parser.parse(str); - const testClass = parser.yy.getClass('Class1'); - expect(testClass.members.length).toBe(2); - expect(testClass.methods.length).toBe(2); - expect(testClass.members[0]).toBe('int : test'); - expect(testClass.members[1]).toBe('string : foo'); - expect(testClass.methods[0]).toBe('test()'); - expect(testClass.methods[1]).toBe('foo()'); - }); - - it('should handle abstract methods', function () { - const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(0); - expect(testClass.members.length).toBe(0); - expect(testClass.methods.length).toBe(1); - expect(testClass.methods[0]).toBe('someMethod()*'); - }); - - it('should handle static methods', function () { - const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.annotations.length).toBe(0); - expect(testClass.members.length).toBe(0); - expect(testClass.methods.length).toBe(1); - expect(testClass.methods[0]).toBe('someMethod()$'); - }); - - it('should associate link and css appropriately', function () { - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'link Class1 "google.com"'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.link).toBe('google.com'); - expect(testClass.cssClasses.length).toBe(1); - expect(testClass.cssClasses[0]).toBe('clickable'); - }); - - it('should associate click and href link and css appropriately', function () { - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 href "google.com"'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.link).toBe('google.com'); - expect(testClass.cssClasses.length).toBe(1); - expect(testClass.cssClasses[0]).toBe('clickable'); - }); - - it('should associate link with tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'link Class1 "google.com" "A tooltip"'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.link).toBe('google.com'); - expect(testClass.tooltip).toBe('A tooltip'); - expect(testClass.cssClasses.length).toBe(1); - expect(testClass.cssClasses[0]).toBe('clickable'); - }); - - it('should associate click and href link with tooltip', function () { - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 href "google.com" "A tooltip"'; - parser.parse(str); - - const testClass = parser.yy.getClass('Class1'); - expect(testClass.link).toBe('google.com'); - expect(testClass.tooltip).toBe('A tooltip'); - expect(testClass.cssClasses.length).toBe(1); - expect(testClass.cssClasses[0]).toBe('clickable'); - }); - - it('should associate click and href link with tooltip and target appropriately', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 href "google.com" "A tooltip" _self'; - parser.parse(str); - - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); - }); - - it('should associate click and href link appropriately', function () { - spyOn(classDb, 'setLink'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 href "google.com"'; - parser.parse(str); - - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); - }); - - it('should associate click and href link with target appropriately', function () { - spyOn(classDb, 'setLink'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 href "google.com" _self'; - parser.parse(str); - - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - }); - - it('should associate link appropriately', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'link Class1 "google.com" "A tooltip" _self'; - parser.parse(str); - - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); - }); - - it('should associate callback appropriately', function () { - spyOn(classDb, 'setClickEvent'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'callback Class1 "functionCall"'; - parser.parse(str); - - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - }); - - it('should associate click and call callback appropriately', function () { - spyOn(classDb, 'setClickEvent'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 call functionCall()'; - parser.parse(str); - - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - }); - - it('should associate callback appropriately with an arbitrary number of args', function () { - spyOn(classDb, 'setClickEvent'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 call functionCall("test0", test1, test2)'; - parser.parse(str); - - expect(classDb.setClickEvent).toHaveBeenCalledWith( - 'Class1', - 'functionCall', - '"test0", test1, test2' - ); - }); - - it('should associate callback with tooltip', function () { - spyOn(classDb, 'setClickEvent'); - spyOn(classDb, 'setTooltip'); - const str = - 'classDiagram\n' + - 'class Class1\n' + - 'Class1 : someMethod()\n' + - 'click Class1 call functionCall() "A tooltip"'; - parser.parse(str); - - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); - }); - }); - - describe('when parsing classDiagram with text labels', () => { - beforeEach(function () { - parser.yy = classDb; - parser.yy.clear(); + const actual = parser.yy.getClass('Class1'); + expect(actual.members.length).toBe(2); + expect(actual.methods.length).toBe(2); + expect(actual.members[0]).toBe('int : test'); + expect(actual.members[1]).toBe('string : foo'); + expect(actual.methods[0]).toBe('test()'); + expect(actual.methods[1]).toBe('foo()'); }); it('should parse a class with a text label', () => { @@ -1053,7 +255,6 @@ C1 --> C2 const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.cssClasses.length).toBe(1); expect(c1.members[0]).toBe('+member1'); expect(c1.cssClasses[0]).toBe('styleClass'); }); @@ -1069,7 +270,6 @@ cssClass "C1" styleClass const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.cssClasses.length).toBe(1); expect(c1.members[0]).toBe('+member1'); expect(c1.cssClasses[0]).toBe('styleClass'); }); @@ -1086,12 +286,10 @@ cssClass "C1,C2" styleClass const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.cssClasses.length).toBe(1); expect(c1.cssClasses[0]).toBe('styleClass'); const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Long long long long long long long long long long label'); - expect(c2.cssClasses.length).toBe(1); expect(c2.cssClasses[0]).toBe('styleClass'); }); @@ -1106,12 +304,10 @@ C1 --> C2 const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.cssClasses.length).toBe(1); expect(c1.cssClasses[0]).toBe('styleClass1'); const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 !@#$%^&*() label'); - expect(c2.cssClasses.length).toBe(1); expect(c2.cssClasses[0]).toBe('styleClass2'); }); @@ -1164,5 +360,630 @@ class C13["With Città foreign language"] expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); expect(classDb.getClass('C13').label).toBe('With Città foreign language'); }); + + it('should handle "note for"', function () { + const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n'; + parser.parse(str); + }); + + it('should handle "note"', function () { + const str = 'classDiagram\n' + 'note "test"\n'; + parser.parse(str); + }); + + it('should handle accTitle and accDescr', function () { + const str = `classDiagram + accTitle: My Title + accDescr: My Description + + Class1 <|-- Class02 + `; + + parser.parse(str); + expect(parser.yy.getAccTitle()).toBe('My Title'); + expect(parser.yy.getAccDescription()).toBe('My Description'); + }); + + it('should handle accTitle and multiline accDescr', function () { + const str = `classDiagram + accTitle: My Title + accDescr { + This is mu multi + line description + } + + Class1 <|-- Class02 + `; + + parser.parse(str); + expect(parser.yy.getAccTitle()).toBe('My Title'); + expect(parser.yy.getAccDescription()).toBe('This is mu multi\nline description'); + }); + }); + + describe('when parsing method definition', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle abstract methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(0); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(1); + expect(actual.methods[0]).toBe('someMethod()*'); + }); + + it('should handle static methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(0); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(1); + expect(actual.methods[0]).toBe('someMethod()$'); + }); + + it('should handle generic types in members', function () { + const str = + 'classDiagram\n' + + 'class Car~T~\n' + + 'Car : -List~Wheel~ wheels\n' + + 'Car : +setWheels(List~Wheel~ wheels)\n' + + 'Car : +getWheels() List~Wheel~'; + + parser.parse(str); + }); + + it('should handle generic types in members in class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Car {\n' + + 'List~Wheel~ wheels\n' + + 'setWheels(List~Wheel~ wheels)\n' + + '+getWheels() List~Wheel~\n' + + '}'; + + parser.parse(str); + }); + }); + + describe('when parsing generics', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle generic class', function () { + const str = + 'classDiagram\n' + + 'class Car~T~\n' + + 'Driver -- Car : drives >\n' + + 'Car *-- Wheel : have 4 >\n' + + 'Car -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle generic class with a literal name', function () { + const str = + 'classDiagram\n' + + 'class `Car`~T~\n' + + 'Driver -- `Car` : drives >\n' + + '`Car` *-- Wheel : have 4 >\n' + + '`Car` -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + + parser.parse(str); + }); + + it('should handle generic class with brackets and a literal name', function () { + const str = + 'classDiagram\n' + + 'class `Dummy_Class`~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + + parser.parse(str); + }); + + it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Dummy_Class {\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + let testPassed = false; + try { + parser.parse(str); + } catch (error) { + testPassed = true; + } + expect(testPassed).toBe(true); + }); + + it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Dummy_Class {\n'; + let testPassed = false; + try { + parser.parse(str); + } catch (error) { + testPassed = true; + } + expect(testPassed).toBe(true); + }); + }); + + describe('when parsing comments', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle comments at the start', function () { + const str = `%% Comment + classDiagram + class Class1 { + int : test + string : foo + test() + foo() + }`; + parser.parse(str); + }); + + it('should handle comments at the end', function () { + const str = `classDiagram +class Class1 { +int : test +string : foo +test() +foo() + +} +%% Comment +`; + + parser.parse(str); + }); + + it('should handle comments at the end no trailing newline', function () { + const str = `classDiagram +class Class1 { +int : test +string : foo +test() +foo() +} +%% Comment`; + + parser.parse(str); + }); + + it('should handle a comment with multiple line feeds', function () { + const str = `classDiagram + + +%% Comment + +class Class1 { +int : test +string : foo +test() +foo() +}`; + + parser.parse(str); + }); + + it('should handle a comment with mermaid class diagram code in them', function () { + const str = `classDiagram +%% Comment Class1 <|-- Class02 +class Class1 { +int : test +string : foo +test() +foo() +}`; + + parser.parse(str); + }); + + it('should handle a comment inside brackets', function () { + const str = + 'classDiagram\n' + + 'class Class1 {\n' + + '%% Comment Class1 <|-- Class02\n' + + 'int : test\n' + + 'string : foo\n' + + 'test()\n' + + 'foo()\n' + + '}'; + + parser.parse(str); + }); + }); + + describe('when parsing relationships', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle relation definitions', function () { + const str = + 'classDiagram\n' + + 'Class1 <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class1'; + + parser.parse(str); + }); + + it('should handle backquoted relation definitions', function () { + const str = + 'classDiagram\n' + + '`Class1` <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class1'; + + parser.parse(str); + }); + + it('should handle relation definitions EXTENSION', function () { + const str = 'classDiagram\n' + 'Class1 <|-- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definition of different types and directions', function () { + const str = + 'classDiagram\n' + + 'Class11 <|.. Class12\n' + + 'Class13 --> Class14\n' + + 'Class15 ..> Class16\n' + + 'Class17 ..|> Class18\n' + + 'Class19 <--* Class20'; + + parser.parse(str); + }); + + it('should handle cardinality and labels', function () { + const str = + 'classDiagram\n' + + 'Class1 "1" *-- "many" Class02 : contains\n' + + 'Class03 o-- Class04 : aggregation\n' + + 'Class05 --> "1" Class06'; + + parser.parse(str); + }); + + it('should handle dashed relation definition of different types and directions', function () { + const str = + 'classDiagram\n' + + 'Class11 <|.. Class12\n' + + 'Class13 <.. Class14\n' + + 'Class15 ..|> Class16\n' + + 'Class17 ..> Class18\n' + + 'Class19 .. Class20'; + parser.parse(str); + }); + + it('should handle relation definitions AGGREGATION and dotted line', function () { + const str = 'classDiagram\n' + 'Class1 o.. Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); + }); + + it('should handle relation definitions COMPOSITION on both sides', function () { + const str = 'classDiagram\n' + 'Class1 *--* Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION); + expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definitions no types', function () { + const str = 'classDiagram\n' + 'Class1 -- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe('none'); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definitions with type only on right side', function () { + const str = 'classDiagram\n' + 'Class1 --|> Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe('none'); + expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle multiple classes and relation definitions', function () { + const str = + 'classDiagram\n' + + 'Class1 <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class10'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class10').id).toBe('Class10'); + + expect(relations.length).toBe(5); + + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[3].relation.type1).toBe('none'); + expect(relations[3].relation.type2).toBe('none'); + expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); + }); + + it('should handle generic class with relation definitions', function () { + const str = 'classDiagram\n' + 'Class1~T~ <|-- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class1').type).toBe('T'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + }); + + describe('when parsing click statements', function () { + beforeEach(function () { + parser.yy = classDb; + }); + it('should handle href link', function () { + spyOn(classDb, 'setLink'); + const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" '; + + parser.parse(str); + + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + + const actual = parser.yy.getClass('Class1'); + expect(actual.link).toBe('google.com'); + expect(actual.cssClasses[0]).toBe('clickable'); + }); + + it('should handle href link with tooltip', function () { + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); + const str = + 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" "A Tooltip" '; + + parser.parse(str); + + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + + const actual = parser.yy.getClass('Class1'); + expect(actual.link).toBe('google.com'); + expect(actual.tooltip).toBe('A Tooltip'); + expect(actual.cssClasses[0]).toBe('clickable'); + }); + + it('should handle href link with tooltip and target', function () { + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); + const str = + 'classDiagram\n' + + 'class Class1\n' + + 'Class1 : someMethod()\n' + + 'click Class1 href "google.com" "A tooltip" _self'; + parser.parse(str); + + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + + const actual = parser.yy.getClass('Class1'); + expect(actual.link).toBe('google.com'); + expect(actual.tooltip).toBe('A tooltip'); + expect(actual.cssClasses[0]).toBe('clickable'); + }); + + it('should handle function call', function () { + spyOn(classDb, 'setClickEvent'); + + const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 call functionCall() '; + + parser.parse(str); + + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + }); + + it('should handle function call with tooltip', function () { + spyOn(classDb, 'setClickEvent'); + spyOn(classDb, 'setTooltip'); + + const str = + 'classDiagram\n' + 'class Class1 \n' + 'click Class1 call functionCall() "A Tooltip" '; + + parser.parse(str); + + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); + }); + + it('should handle function call with an arbitrary number of args', function () { + spyOn(classDb, 'setClickEvent'); + const str = + 'classDiagram\n' + + 'class Class1\n' + + 'Class1 : someMethod()\n' + + 'click Class1 call functionCall(test, test1, test2)'; + parser.parse(str); + + expect(classDb.setClickEvent).toHaveBeenCalledWith( + 'Class1', + 'functionCall', + 'test, test1, test2' + ); + }); + + it('should handle function call with an arbitrary number of args and tooltip', function () { + spyOn(classDb, 'setClickEvent'); + spyOn(classDb, 'setTooltip'); + const str = + 'classDiagram\n' + + 'class Class1\n' + + 'Class1 : someMethod()\n' + + 'click Class1 call functionCall("test0", test1, test2) "A Tooltip"'; + parser.parse(str); + + expect(classDb.setClickEvent).toHaveBeenCalledWith( + 'Class1', + 'functionCall', + '"test0", test1, test2' + ); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); + }); + }); + + describe('when parsing annotations', function () { + beforeEach(function () { + parser.yy = classDb; + parser.yy.clear(); + }); + + it('should handle class annotations', function () { + const str = 'classDiagram\n' + 'class Class1\n' + '<> Class1'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(1); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(0); + expect(actual.annotations[0]).toBe('interface'); + }); + + it('should handle class annotations with members and methods', function () { + const str = + 'classDiagram\n' + + 'class Class1\n' + + 'Class1 : int test\n' + + 'Class1 : test()\n' + + '<> Class1'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(1); + expect(actual.members.length).toBe(1); + expect(actual.methods.length).toBe(1); + expect(actual.annotations[0]).toBe('interface'); + }); + + it('should handle class annotations in brackets', function () { + const str = 'classDiagram\n' + 'class Class1 {\n' + '<>\n' + '}'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(1); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(0); + expect(actual.annotations[0]).toBe('interface'); + }); + + it('should handle class annotations in brackets with members and methods', function () { + const str = + 'classDiagram\n' + + 'class Class1 {\n' + + '<>\n' + + 'int : test\n' + + 'test()\n' + + '}'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(1); + expect(actual.members.length).toBe(1); + expect(actual.methods.length).toBe(1); + expect(actual.annotations[0]).toBe('interface'); + }); }); }); diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 0c9ad2f2a..4354c54be 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -200,7 +200,7 @@ Function arguments are optional: 'call ()' simply executes 'callb start : mermaidDoc - | statments + | statements | direction | directive start ; From 8c4fb6c753abf92c88c643eded2e44c71886cc12 Mon Sep 17 00:00:00 2001 From: eitsupi <50911393+eitsupi@users.noreply.github.com> Date: Sat, 15 Apr 2023 16:49:32 +0900 Subject: [PATCH 15/64] docs(integrations): list quarto --- packages/mermaid/src/docs/ecosystem/integrations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mermaid/src/docs/ecosystem/integrations.md b/packages/mermaid/src/docs/ecosystem/integrations.md index a0a904425..3dbd943bb 100644 --- a/packages/mermaid/src/docs/ecosystem/integrations.md +++ b/packages/mermaid/src/docs/ecosystem/integrations.md @@ -155,6 +155,7 @@ They also serve as proof of concept, for the variety of things that can be built - [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin) - [mdbook](https://rust-lang.github.io/mdBook/index.html) - [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid) +- [Quarto](https://quarto.org/) ## Browser Extensions From 432e8d6535a4869fe4503d13264382838bb386f5 Mon Sep 17 00:00:00 2001 From: eitsupi <50911393+eitsupi@users.noreply.github.com> Date: Sat, 15 Apr 2023 17:01:51 +0900 Subject: [PATCH 16/64] docs: reflect source changes --- docs/ecosystem/integrations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ecosystem/integrations.md b/docs/ecosystem/integrations.md index 9cd8bc9a5..df58afe67 100644 --- a/docs/ecosystem/integrations.md +++ b/docs/ecosystem/integrations.md @@ -161,6 +161,7 @@ They also serve as proof of concept, for the variety of things that can be built - [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin) - [mdbook](https://rust-lang.github.io/mdBook/index.html) - [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid) +- [Quarto](https://quarto.org/) ## Browser Extensions From 59a85a7dfd4ce29bdc7943299b64b9865a08e48b Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 16 Apr 2023 19:42:51 -0700 Subject: [PATCH 17/64] Multiple Fixes to classes --- docs/syntax/classDiagram.md | 10 +- .../mermaid/src/diagrams/class/classDb.ts | 5 +- .../src/diagrams/class/classDiagram.spec.ts | 1095 +++++++++-------- .../diagrams/class/parser/classDiagram.jison | 6 +- .../mermaid/src/diagrams/class/svgDraw.js | 67 +- .../src/diagrams/class/svgDraw.spec.js | 339 +++-- .../mermaid/src/diagrams/common/common.ts | 4 +- .../mermaid/src/docs/syntax/classDiagram.md | 10 +- 8 files changed, 884 insertions(+), 652 deletions(-) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 69144ef39..7dd9bd6b3 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -128,7 +128,7 @@ classDiagram Vehicle <|-- Car ``` -Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores. +Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-). ### Class labels @@ -283,12 +283,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function - `#` Protected - `~` Package/Internal -> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`: +> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type: > -> - `*` Abstract e.g.: `someAbstractMethod()*` -> - `$` Static e.g.: `someStaticMethod()$` +> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` +> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$` -> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name: +> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end: > > - `$` Static e.g.: `String someField$` diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 8fa1eeb26..a2121f69e 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -106,6 +106,7 @@ export const clear = function () { export const getClass = function (id: string) { return classes[id]; }; + export const getClasses = function () { return classes; }; @@ -170,9 +171,10 @@ export const addMember = function (className: string, member: string) { const memberString = member.trim(); if (memberString.startsWith('<<') && memberString.endsWith('>>')) { - // Remove leading and trailing brackets + // its an annotation theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); } else if (memberString.indexOf(')') > 0) { + //its a method theClass.methods.push(sanitizeText(memberString)); } else if (memberString) { theClass.members.push(sanitizeText(memberString)); @@ -234,6 +236,7 @@ const setTooltip = function (ids: string, tooltip?: string) { } }); }; + export const getTooltip = function (id: string) { return classes[id].tooltip; }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 060ba6911..8d8eca0f1 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -4,11 +4,33 @@ import classDb from './classDb.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; -describe('given a class diagram, ', function () { +describe('given a basic class diagram, ', function () { describe('when parsing class definition', function () { beforeEach(function () { parser.yy = classDb; }); + it('should handle accTitle and accDescr', function () { + const str = `classDiagram + accTitle: My Title + accDescr: My Description`; + + parser.parse(str); + expect(parser.yy.getAccTitle()).toBe('My Title'); + expect(parser.yy.getAccDescription()).toBe('My Description'); + }); + + it('should handle accTitle and multiline accDescr', function () { + const str = `classDiagram + accTitle: My Title + accDescr { + This is my multi + line description + }`; + + parser.parse(str); + expect(parser.yy.getAccTitle()).toBe('My Title'); + expect(parser.yy.getAccDescription()).toBe('This is my multi\nline description'); + }); it.skip('should handle a leading newline', function () { const str = '\nclassDiagram\n' + 'class Car'; @@ -27,113 +49,20 @@ describe('given a class diagram, ', function () { parser.parse(str); }); + it('should handle class names with dash', function () { + const str = 'classDiagram\n' + 'class Ca-r'; + + parser.parse(str); + const actual = classDb.getClass('Ca-r'); + expect(actual.label).toBe('Ca-r'); + }); + it('should handle class names with underscore', function () { const str = 'classDiagram\n' + 'class `A_Car`'; parser.parse(str); }); - it('should handle basic class definitions', function () { - const str = - 'classDiagram\n' + - 'class Car\n' + - 'Driver -- Car : drives >\n' + - 'Car *-- Wheel : have 4 >\n' + - 'Car -- Person : < owns'; - - parser.parse(str); - }); - - it('should handle member definitions in brackets', function () { - const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}'; - - parser.parse(str); - }); - - it('should handle method declaration in brackets', function () { - const str = 'classDiagram\n' + 'class Car{\n' + '+size()\n' + '}'; - - parser.parse(str); - }); - - it('should handle properties in brackets, and some outside', function () { - const str = - 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}\n' + 'Car : +ArrayList size()\n'; - - parser.parse(str); - }); - - it('should handle method statements', function () { - const str = - 'classDiagram\n' + - 'Object <|-- ArrayList\n' + - 'Object : equals()\n' + - 'ArrayList : Object[] elementData\n' + - 'ArrayList : size()'; - - parser.parse(str); - }); - - it('should handle visibility for methods and members', function () { - const str = - 'classDiagram\n' + - 'class actual\n' + - 'actual : -int privateMember\n' + - 'actual : +int publicMember\n' + - 'actual : #int protectedMember\n' + - 'actual : -privateMethod()\n' + - 'actual : +publicMethod()\n' + - 'actual : #protectedMethod()\n'; - - parser.parse(str); - }); - - it('should handle parsing of method statements grouped by brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - - parser.parse(str); - }); - - it('should handle return types on methods', function () { - const str = - 'classDiagram\n' + - 'Object <|-- ArrayList\n' + - 'Object : equals()\n' + - 'Object : -Object[] objects\n' + - 'Object : +getObjects() Object[]\n' + - 'ArrayList : Dummy elementData\n' + - 'ArrayList : getDummy() Dummy'; - - parser.parse(str); - }); - - it('should handle return types on methods grouped by brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class {\n' + - 'string data\n' + - 'getDummy() Dummy\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' int flightNumber\n' + - ' datetime departureTime\n' + - ' getDepartureTime() datetime\n' + - '}'; - - parser.parse(str); - }); - it('should handle parsing of separators', function () { const str = 'classDiagram\n' + @@ -167,122 +96,90 @@ describe('given a class diagram, ', function () { parser.parse(str); }); - it('should add bracket members in right order', function () { - const str = - 'classDiagram\n' + - 'class Class1 {\n' + - 'int : test\n' + - 'string : foo\n' + - 'test()\n' + - 'foo()\n' + - '}'; + it('should parse a class with a text label', () => { + const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]'; + parser.parse(str); - const actual = parser.yy.getClass('Class1'); - expect(actual.members.length).toBe(2); - expect(actual.methods.length).toBe(2); - expect(actual.members[0]).toBe('int : test'); - expect(actual.members[1]).toBe('string : foo'); - expect(actual.methods[0]).toBe('test()'); - expect(actual.methods[1]).toBe('foo()'); - }); - - it('should parse a class with a text label', () => { - parser.parse(`classDiagram - class C1["Class 1 with text label"] - C1 --> C2 - `); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classDb.getClass('C2'); - expect(c2.label).toBe('C2'); }); - it('should parse two classes with text labels', () => { - parser.parse(`classDiagram - class C1["Class 1 with text label"] - class C2["Class 2 with chars @?"] - C1 --> C2 - `); + it('should parse two classes with text labels', function () { + const str = + 'classDiagram\n' + + 'class C1["Class 1 with text label"]\n' + + 'class C2["Class 2 with chars @?"]\n'; + + parser.parse(str); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 with chars @?'); }); - it('should parse a class with a text label and members', () => { - parser.parse(`classDiagram - class C1["Class 1 with text label"] { - +member1 - } - C1 --> C2 - `); + it('should parse a class with a text label and member', () => { + const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1'; + + parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); - expect(c1.members[0]).toBe('+member1'); - - const c2 = classDb.getClass('C2'); - expect(c2.label).toBe('C2'); + expect(c1.members[0]).toBe('member1'); }); - it('should parse a class with a text label, members and annotation', () => { - parser.parse(`classDiagram - class C1["Class 1 with text label"] { - <> - +member1 - } - C1 --> C2 - `); + it('should parse a class with a text label, member and annotation', () => { + const str = + 'classDiagram\n' + + 'class C1["Class 1 with text label"]\n' + + '<> C1\n' + + 'C1 : int member1'; + + parser.parse(str); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); - expect(c1.members[0]).toBe('+member1'); + expect(c1.members[0]).toBe('int member1'); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); - - const c2 = classDb.getClass('C2'); - expect(c2.label).toBe('C2'); }); it('should parse a class with text label and css class shorthand', () => { - parser.parse(`classDiagram -class C1["Class 1 with text label"]:::styleClass { - +member1 -} -C1 --> C2 - `); + const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]:::styleClass'; + + parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members[0]).toBe('+member1'); expect(c1.cssClasses[0]).toBe('styleClass'); }); it('should parse a class with text label and css class', () => { - parser.parse(`classDiagram -class C1["Class 1 with text label"] { - +member1 -} -C1 --> C2 -cssClass "C1" styleClass - `); + const str = + 'classDiagram\n' + + 'class C1["Class 1 with text label"]\n' + + 'C1 : int member1\n' + + 'cssClass "C1" styleClass'; + + parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members[0]).toBe('+member1'); + expect(c1.members[0]).toBe('int member1'); expect(c1.cssClasses[0]).toBe('styleClass'); }); it('should parse two classes with text labels and css classes', () => { - parser.parse(`classDiagram -class C1["Class 1 with text label"] { - +member1 -} -class C2["Long long long long long long long long long long label"] -C1 --> C2 -cssClass "C1,C2" styleClass - `); + const str = + 'classDiagram\n' + + 'class C1["Class 1 with text label"]\n' + + 'C1 : int member1\n' + + 'class C2["Long long long long long long long long long long label"]\n' + + 'cssClass "C1,C2" styleClass'; + + parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); @@ -294,13 +191,12 @@ cssClass "C1,C2" styleClass }); it('should parse two classes with text labels and css class shorthands', () => { - parser.parse(`classDiagram -class C1["Class 1 with text label"]:::styleClass1 { - +member1 -} -class C2["Class 2 !@#$%^&*() label"]:::styleClass2 -C1 --> C2 - `); + const str = + 'classDiagram\n' + + 'class C1["Class 1 with text label"]:::styleClass1\n' + + 'class C2["Class 2 !@#$%^&*() label"]:::styleClass2'; + + parser.parse(str); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); @@ -315,10 +211,7 @@ C1 --> C2 parser.parse(`classDiagram class C1["Class with text label"] class C2["Class with text label"] -class C3["Class with text label"] -C1 --> C2 -C3 ..> C2 - `); +class C3["Class with text label"]`); const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class with text label'); @@ -370,185 +263,96 @@ class C13["With Città foreign language"] const str = 'classDiagram\n' + 'note "test"\n'; parser.parse(str); }); - - it('should handle accTitle and accDescr', function () { - const str = `classDiagram - accTitle: My Title - accDescr: My Description - - Class1 <|-- Class02 - `; - - parser.parse(str); - expect(parser.yy.getAccTitle()).toBe('My Title'); - expect(parser.yy.getAccDescription()).toBe('My Description'); - }); - - it('should handle accTitle and multiline accDescr', function () { - const str = `classDiagram - accTitle: My Title - accDescr { - This is mu multi - line description - } - - Class1 <|-- Class02 - `; - - parser.parse(str); - expect(parser.yy.getAccTitle()).toBe('My Title'); - expect(parser.yy.getAccDescription()).toBe('This is mu multi\nline description'); - }); }); - describe('when parsing method definition', function () { + describe('when parsing class defined in brackets', function () { beforeEach(function () { parser.yy = classDb; }); - it('should handle abstract methods', function () { - const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*'; + it('should handle member definitions', function () { + const str = 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}'; + + parser.parse(str); + }); + + it('should handle method definitions', function () { + const str = 'classDiagram\n' + 'class Car{\n' + '+size()\n' + '}'; + + parser.parse(str); + }); + + it('should handle a mix of members defined in and outside of brackets', function () { + const str = + 'classDiagram\n' + 'class Car{\n' + '+int wheels\n' + '}\n' + 'Car : +ArrayList size()\n'; + + parser.parse(str); + }); + + it('should handle member and method definitions', () => { + const str = + 'classDiagram\n' + 'class Dummy_Class {\n' + 'String data\n' + 'void methods()\n' + '}'; + + parser.parse(str); + }); + + it('should handle return types on methods', () => { + const str = + 'classDiagram\n' + + 'class Flight {\n' + + 'int flightNumber\n' + + 'datetime departureTime\n' + + 'getDepartureTime() datetime\n' + + '}'; + + parser.parse(str); + }); + + it('should add bracket members in right order', () => { + const str = + 'classDiagram\n' + + 'class Class1 {\n' + + 'int testMember\n' + + 'string fooMember\n' + + 'test()\n' + + 'foo()\n' + + '}'; parser.parse(str); const actual = parser.yy.getClass('Class1'); - expect(actual.annotations.length).toBe(0); - expect(actual.members.length).toBe(0); - expect(actual.methods.length).toBe(1); - expect(actual.methods[0]).toBe('someMethod()*'); + expect(actual.members.length).toBe(2); + expect(actual.methods.length).toBe(2); + expect(actual.members[0]).toBe('int testMember'); + expect(actual.members[1]).toBe('string fooMember'); + expect(actual.methods[0]).toBe('test()'); + expect(actual.methods[1]).toBe('foo()'); }); - it('should handle static methods', function () { - const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$'; - parser.parse(str); - - const actual = parser.yy.getClass('Class1'); - expect(actual.annotations.length).toBe(0); - expect(actual.members.length).toBe(0); - expect(actual.methods.length).toBe(1); - expect(actual.methods[0]).toBe('someMethod()$'); - }); - - it('should handle generic types in members', function () { - const str = - 'classDiagram\n' + - 'class Car~T~\n' + - 'Car : -List~Wheel~ wheels\n' + - 'Car : +setWheels(List~Wheel~ wheels)\n' + - 'Car : +getWheels() List~Wheel~'; + it('should parse a class with a text label and members', () => { + const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}'; parser.parse(str); + const c1 = classDb.getClass('C1'); + expect(c1.label).toBe('Class 1 with text label'); + expect(c1.members.length).toBe(1); + expect(c1.members[0]).toBe('+member1'); }); - it('should handle generic types in members in class with brackets', function () { + it('should parse a class with a text label, members and annotation', () => { const str = 'classDiagram\n' + - 'class Car {\n' + - 'List~Wheel~ wheels\n' + - 'setWheels(List~Wheel~ wheels)\n' + - '+getWheels() List~Wheel~\n' + + 'class C1["Class 1 with text label"] {\n' + + '<>\n' + + '+member1\n' + '}'; parser.parse(str); - }); - }); - - describe('when parsing generics', function () { - beforeEach(function () { - parser.yy = classDb; - }); - - it('should handle generic class', function () { - const str = - 'classDiagram\n' + - 'class Car~T~\n' + - 'Driver -- Car : drives >\n' + - 'Car *-- Wheel : have 4 >\n' + - 'Car -- Person : < owns'; - - parser.parse(str); - }); - - it('should handle generic class with a literal name', function () { - const str = - 'classDiagram\n' + - 'class `Car`~T~\n' + - 'Driver -- `Car` : drives >\n' + - '`Car` *-- Wheel : have 4 >\n' + - '`Car` -- Person : < owns'; - - parser.parse(str); - }); - - it('should handle generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - - parser.parse(str); - }); - - it('should handle generic class with brackets and a literal name', function () { - const str = - 'classDiagram\n' + - 'class `Dummy_Class`~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - - parser.parse(str); - }); - - it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Dummy_Class {\n' + - 'class Flight {\n' + - ' flightNumber : Integer\n' + - ' departureTime : Date\n' + - '}'; - let testPassed = false; - try { - parser.parse(str); - } catch (error) { - testPassed = true; - } - expect(testPassed).toBe(true); - }); - - it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () { - const str = - 'classDiagram\n' + - 'class Dummy_Class~T~ {\n' + - 'String data\n' + - ' void methods()\n' + - '}\n' + - '\n' + - 'class Dummy_Class {\n'; - let testPassed = false; - try { - parser.parse(str); - } catch (error) { - testPassed = true; - } - expect(testPassed).toBe(true); + const c1 = classDb.getClass('C1'); + expect(c1.label).toBe('Class 1 with text label'); + expect(c1.members.length).toBe(1); + expect(c1.members[0]).toBe('+member1'); + expect(c1.annotations.length).toBe(1); + expect(c1.annotations[0]).toBe('interface'); }); }); @@ -641,180 +445,6 @@ foo() }); }); - describe('when parsing relationships', function () { - beforeEach(function () { - parser.yy = classDb; - }); - - it('should handle relation definitions', function () { - const str = - 'classDiagram\n' + - 'Class1 <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class1'; - - parser.parse(str); - }); - - it('should handle backquoted relation definitions', function () { - const str = - 'classDiagram\n' + - '`Class1` <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class1'; - - parser.parse(str); - }); - - it('should handle relation definitions EXTENSION', function () { - const str = 'classDiagram\n' + 'Class1 <|-- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle relation definition of different types and directions', function () { - const str = - 'classDiagram\n' + - 'Class11 <|.. Class12\n' + - 'Class13 --> Class14\n' + - 'Class15 ..> Class16\n' + - 'Class17 ..|> Class18\n' + - 'Class19 <--* Class20'; - - parser.parse(str); - }); - - it('should handle cardinality and labels', function () { - const str = - 'classDiagram\n' + - 'Class1 "1" *-- "many" Class02 : contains\n' + - 'Class03 o-- Class04 : aggregation\n' + - 'Class05 --> "1" Class06'; - - parser.parse(str); - }); - - it('should handle dashed relation definition of different types and directions', function () { - const str = - 'classDiagram\n' + - 'Class11 <|.. Class12\n' + - 'Class13 <.. Class14\n' + - 'Class15 ..|> Class16\n' + - 'Class17 ..> Class18\n' + - 'Class19 .. Class20'; - parser.parse(str); - }); - - it('should handle relation definitions AGGREGATION and dotted line', function () { - const str = 'classDiagram\n' + 'Class1 o.. Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); - }); - - it('should handle relation definitions COMPOSITION on both sides', function () { - const str = 'classDiagram\n' + 'Class1 *--* Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION); - expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle relation definitions no types', function () { - const str = 'classDiagram\n' + 'Class1 -- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe('none'); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle relation definitions with type only on right side', function () { - const str = 'classDiagram\n' + 'Class1 --|> Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe('none'); - expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - - it('should handle multiple classes and relation definitions', function () { - const str = - 'classDiagram\n' + - 'Class1 <|-- Class02\n' + - 'Class03 *-- Class04\n' + - 'Class05 o-- Class06\n' + - 'Class07 .. Class08\n' + - 'Class09 -- Class10'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class10').id).toBe('Class10'); - - expect(relations.length).toBe(5); - - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - expect(relations[3].relation.type1).toBe('none'); - expect(relations[3].relation.type2).toBe('none'); - expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); - }); - - it('should handle generic class with relation definitions', function () { - const str = 'classDiagram\n' + 'Class1~T~ <|-- Class02'; - - parser.parse(str); - - const relations = parser.yy.getRelations(); - - expect(parser.yy.getClass('Class1').id).toBe('Class1'); - expect(parser.yy.getClass('Class1').type).toBe('T'); - expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); - expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); - }); - }); - describe('when parsing click statements', function () { beforeEach(function () { parser.yy = classDb; @@ -987,3 +617,422 @@ foo() }); }); }); + +describe('given a class diagram with members and methods ', function () { + describe('when parsing members', function () { + beforeEach(function () { + parser.yy = classDb; + parser.yy.clear(); + }); + + it('should handle simple member declaration', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : wheels'; + + parser.parse(str); + }); + + it('should handle simple member declaration with type', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels'; + + parser.parse(str); + }); + + it('should handle visibility', function () { + const str = + 'classDiagram\n' + + 'class actual\n' + + 'actual : -int privateMember\n' + + 'actual : +int publicMember\n' + + 'actual : #int protectedMember'; + + parser.parse(str); + }); + + it('should handle generic types', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : -List~Wheel~ wheels'; + + parser.parse(str); + }); + }); + + describe('when parsing method definition', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle method definition', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : GetSize()'; + + parser.parse(str); + }); + + it('should handle simple return types', function () { + const str = 'classDiagram\n' + 'class Object\n' + 'Object : getObject() Object'; + + parser.parse(str); + }); + + it('should handle return types as array', function () { + const str = 'classDiagram\n' + 'class Object\n' + 'Object : getObjects() Object[]'; + + parser.parse(str); + }); + + it('should handle visibility', function () { + const str = + 'classDiagram\n' + + 'class actual\n' + + 'actual : -privateMethod()\n' + + 'actual : +publicMethod()\n' + + 'actual : #protectedMethod()\n'; + + parser.parse(str); + }); + + it('should handle abstract methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(0); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(1); + expect(actual.methods[0]).toBe('someMethod()*'); + }); + + it('should handle static methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$'; + parser.parse(str); + + const actual = parser.yy.getClass('Class1'); + expect(actual.annotations.length).toBe(0); + expect(actual.members.length).toBe(0); + expect(actual.methods.length).toBe(1); + expect(actual.methods[0]).toBe('someMethod()$'); + }); + + it('should handle generic types in arguments', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : +setWheels(List~Wheel~ wheels)'; + parser.parse(str); + }); + + it('should handle generic return types', function () { + const str = 'classDiagram\n' + 'class Car\n' + 'Car : +getWheels() List~Wheel~'; + + parser.parse(str); + }); + + it('should handle generic types in members in class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Car {\n' + + 'List~Wheel~ wheels\n' + + 'setWheels(List~Wheel~ wheels)\n' + + '+getWheels() List~Wheel~\n' + + '}'; + + parser.parse(str); + }); + }); +}); + +describe('given a class diagram with generics, ', function () { + describe('when parsing valid generic classes', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle generic class', function () { + const str = 'classDiagram\n' + 'class Car~T~'; + + parser.parse(str); + }); + + it('should handle generic class with relationships', function () { + const str = + 'classDiagram\n' + + 'class Car~T~\n' + + 'Driver -- Car : drives >\n' + + 'Car *-- Wheel : have 4 >\n' + + 'Car -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle generic class with a literal name', function () { + const str = + 'classDiagram\n' + + 'class `Car`~T~\n' + + 'Driver -- `Car` : drives >\n' + + '`Car` *-- Wheel : have 4 >\n' + + '`Car` -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + 'void methods()\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + 'Integer flightNumber\n' + + 'Date departureTime\n' + + '}'; + + parser.parse(str); + }); + + it('should handle generic class with brackets and a literal name', function () { + const str = + 'classDiagram\n' + + 'class `Dummy_Class`~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + + parser.parse(str); + }); + }); + + describe('when parsing invalid generic classes', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Dummy_Class {\n' + + 'class Flight {\n' + + ' flightNumber : Integer\n' + + ' departureTime : Date\n' + + '}'; + let testPassed = false; + try { + parser.parse(str); + } catch (error) { + testPassed = true; + } + expect(testPassed).toBe(true); + }); + + it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () { + const str = + 'classDiagram\n' + + 'class Dummy_Class~T~ {\n' + + 'String data\n' + + ' void methods()\n' + + '}\n' + + '\n' + + 'class Dummy_Class {\n'; + let testPassed = false; + try { + parser.parse(str); + } catch (error) { + testPassed = true; + } + expect(testPassed).toBe(true); + }); + }); +}); + +describe('given a class diagram with relationships, ', function () { + describe('when parsing basic relationships', function () { + beforeEach(function () { + parser.yy = classDb; + }); + + it('should handle all basic relationships', function () { + const str = + 'classDiagram\n' + + 'Class1 <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class1'; + + parser.parse(str); + }); + + it('should handle backquoted class name', function () { + const str = + 'classDiagram\n' + + '`Class1` <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class1'; + + parser.parse(str); + }); + + it('should handle generics', function () { + const str = 'classDiagram\n' + 'Class1~T~ <|-- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class1').type).toBe('T'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relationships with labels', function () { + const str = + 'classDiagram\n' + + 'class Car\n' + + 'Driver -- Car : drives >\n' + + 'Car *-- Wheel : have 4 >\n' + + 'Car -- Person : < owns'; + + parser.parse(str); + }); + + it('should handle relation definitions EXTENSION', function () { + const str = 'classDiagram\n' + 'Class1 <|-- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definition of different types and directions', function () { + const str = + 'classDiagram\n' + + 'Class11 <|.. Class12\n' + + 'Class13 --> Class14\n' + + 'Class15 ..> Class16\n' + + 'Class17 ..|> Class18\n' + + 'Class19 <--* Class20'; + + parser.parse(str); + }); + + it('should handle cardinality and labels', function () { + const str = + 'classDiagram\n' + + 'Class1 "1" *-- "many" Class02 : contains\n' + + 'Class03 o-- Class04 : aggregation\n' + + 'Class05 --> "1" Class06'; + + parser.parse(str); + }); + + it('should handle dashed relation definition of different types and directions', function () { + const str = + 'classDiagram\n' + + 'Class11 <|.. Class12\n' + + 'Class13 <.. Class14\n' + + 'Class15 ..|> Class16\n' + + 'Class17 ..> Class18\n' + + 'Class19 .. Class20'; + parser.parse(str); + }); + + it('should handle relation definitions AGGREGATION and dotted line', function () { + const str = 'classDiagram\n' + 'Class1 o.. Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); + }); + + it('should handle relation definitions COMPOSITION on both sides', function () { + const str = 'classDiagram\n' + 'Class1 *--* Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION); + expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definitions with no types', function () { + const str = 'classDiagram\n' + 'Class1 -- Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe('none'); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle relation definitions with type only on right side', function () { + const str = 'classDiagram\n' + 'Class1 --|> Class02'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class02').id).toBe('Class02'); + expect(relations[0].relation.type1).toBe('none'); + expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + }); + + it('should handle multiple classes and relation definitions', function () { + const str = + 'classDiagram\n' + + 'Class1 <|-- Class02\n' + + 'Class03 *-- Class04\n' + + 'Class05 o-- Class06\n' + + 'Class07 .. Class08\n' + + 'Class09 -- Class10'; + + parser.parse(str); + + const relations = parser.yy.getRelations(); + + expect(parser.yy.getClass('Class1').id).toBe('Class1'); + expect(parser.yy.getClass('Class10').id).toBe('Class10'); + + expect(relations.length).toBe(5); + + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type2).toBe('none'); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[3].relation.type1).toBe('none'); + expect(relations[3].relation.type2).toBe('none'); + expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 4354c54be..6a7834eca 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -259,8 +259,8 @@ className : alphaNumToken { $$=$1; } | classLiteralName { $$=$1; } | alphaNumToken className { $$=$1+$2; } - | alphaNumToken GENERICTYPE { $$=$1+'~'+$2; } - | classLiteralName GENERICTYPE { $$=$1+'~'+$2; } + | alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; } + | classLiteralName GENERICTYPE { $$=$1+'~'+$2+'~'; } ; statement @@ -366,7 +366,7 @@ textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFA textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ; -alphaNumToken : UNICODE_TEXT | NUM | ALPHA; +alphaNumToken : UNICODE_TEXT | NUM | ALPHA | MINUS; classLiteralName : BQUOTE_STR; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 3ce8e980b..7206506a2 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -199,11 +199,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) { isFirst = false; }); - let classTitleString = classDef.id; - - if (classDef.type !== undefined && classDef.type !== '') { - classTitleString += '<' + classDef.type + '>'; - } + let classTitleString = getClassTitleString(classDef); const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title'); @@ -291,6 +287,16 @@ export const drawClass = function (elem, classDef, conf, diagObj) { return classInfo; }; +export const getClassTitleString = function (classDef) { + let classTitleString = classDef.id; + + if (classDef.type) { + classTitleString += '<' + classDef.type + '>'; + } + + return classTitleString; +}; + /** * Renders a note diagram * @@ -355,6 +361,9 @@ export const drawNote = function (elem, note, conf, diagObj) { }; export const parseMember = function (text) { + // Note: these two regular expressions don't parse the official UML syntax for attributes + // and methods. They parse a Java-style syntax of the form + // "String name" (for attributes) and "String name(int x)" for methods const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/; const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/; @@ -421,33 +430,48 @@ const buildLegacyDisplay = function (text) { let displayText = ''; let cssStyle = ''; let returnType = ''; + + let visibility = ''; + let firstChar = text.substring(0, 1); + let lastChar = text.substring(text.length - 1, text.length); + + if (firstChar.match(/[#+~-]/)) { + visibility = firstChar; + } + + let noClassifierRe = /[\s\w)~]/; + if (!lastChar.match(noClassifierRe)) { + cssStyle = parseClassifier(lastChar); + } + + let startIndex = visibility === '' ? 0 : 1; + let endIndex = cssStyle === '' ? text.length : text.length - 1; + text = text.substring(startIndex, endIndex); + let methodStart = text.indexOf('('); let methodEnd = text.indexOf(')'); if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) { - let visibility = ''; - let methodName = ''; - - let firstChar = text.substring(0, 1); - if (firstChar.match(/\w/)) { - methodName = text.substring(0, methodStart).trim(); - } else { - if (firstChar.match(/[#+~-]/)) { - visibility = firstChar; - } - - methodName = text.substring(1, methodStart).trim(); - } + let methodName = text.substring(0, methodStart).trim(); const parameters = text.substring(methodStart + 1, methodEnd); - const classifier = text.substring(methodEnd + 1, 1); - cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2)); displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; if (methodEnd < text.length) { - returnType = text.substring(methodEnd + 2).trim(); + // special case: classifier after the closing parenthesis + let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2); + if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) { + cssStyle = parseClassifier(potentialClassifier); + returnType = text.substring(methodEnd + 2).trim(); + } else { + returnType = text.substring(methodEnd + 1).trim(); + } + if (returnType !== '') { + if (returnType.charAt(0) === ':') { + returnType = returnType.substring(1).trim(); + } returnType = ' : ' + parseGenericTypes(returnType); displayText += returnType; } @@ -502,6 +526,7 @@ const parseClassifier = function (classifier) { }; export default { + getClassTitleString, drawClass, drawEdge, drawNote, diff --git a/packages/mermaid/src/diagrams/class/svgDraw.spec.js b/packages/mermaid/src/diagrams/class/svgDraw.spec.js index 2e7c64fa0..e8ba9f7e1 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.spec.js @@ -1,8 +1,19 @@ import svgDraw from './svgDraw.js'; -describe('class member Renderer, ', function () { - describe('when parsing text to build method display string', function () { - it('should handle simple method declaration', function () { +describe('given a string representing class method, ', function () { + it('should handle class names with generics', function () { + const classDef = { + id: 'Car', + type: 'T', + label: 'Car', + }; + + let actual = svgDraw.getClassTitleString(classDef); + expect(actual).toBe('Car'); + }); + + describe('when parsing base method declaration', function () { + it('should handle simple declaration', function () { const str = 'foo()'; let actual = svgDraw.parseMember(str); @@ -10,71 +21,7 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle public visibility', function () { - const str = '+foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('+foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle private visibility', function () { - const str = '-foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('-foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle protected visibility', function () { - const str = '#foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('#foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle package/internal visibility', function () { - const str = '~foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('~foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should ignore unknown character for visibility', function () { - const str = '!foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle abstract method classifier', function () { - const str = 'foo()*'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo()'); - expect(actual.cssStyle).toBe('font-style:italic;'); - }); - - it('should handle static method classifier', function () { - const str = 'foo()$'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo()'); - expect(actual.cssStyle).toBe('text-decoration:underline;'); - }); - - it('should ignore unknown character for classifier', function () { - const str = 'foo()!'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle simple method declaration with parameters', function () { + it('should handle declaration with parameters', function () { const str = 'foo(int id)'; let actual = svgDraw.parseMember(str); @@ -82,7 +29,7 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle simple method declaration with multiple parameters', function () { + it('should handle declaration with multiple parameters', function () { const str = 'foo(int id, object thing)'; let actual = svgDraw.parseMember(str); @@ -90,7 +37,7 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle simple method declaration with single item in parameters', function () { + it('should handle declaration with single item in parameters', function () { const str = 'foo(id)'; let actual = svgDraw.parseMember(str); @@ -98,7 +45,7 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle simple method declaration with single item in parameters with extra spaces', function () { + it('should handle declaration with single item in parameters with extra spaces', function () { const str = ' foo ( id) '; let actual = svgDraw.parseMember(str); @@ -106,22 +53,6 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle method declaration with return value', function () { - const str = 'foo(id) int'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(id) : int'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle method declaration with generic return value', function () { - const str = 'foo(id) List~int~'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(id) : List'); - expect(actual.cssStyle).toBe(''); - }); - it('should handle method declaration with generic parameter', function () { const str = 'foo(List~int~)'; let actual = svgDraw.parseMember(str); @@ -130,6 +61,46 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); + it('should handle method declaration with normal and generic parameter', function () { + const str = 'foo(int, List~int~)'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(int, List)'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle declaration with return value', function () { + const str = 'foo(id) int'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(id) : int'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle declaration with colon return value', function () { + const str = 'foo(id) : int'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(id) : int'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle declaration with generic return value', function () { + const str = 'foo(id) List~int~'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(id) : List'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle declaration with colon generic return value', function () { + const str = 'foo(id) : List~int~'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(id) : List'); + expect(actual.cssStyle).toBe(''); + }); + it('should handle method declaration with all possible markup', function () { const str = '+foo ( List~int~ ids )* List~Item~'; let actual = svgDraw.parseMember(str); @@ -138,7 +109,7 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe('font-style:italic;'); }); - it('should handle method declaration with nested markup', function () { + it('should handle method declaration with nested generics', function () { const str = '+foo ( List~List~int~~ ids )* List~List~Item~~'; let actual = svgDraw.parseMember(str); @@ -147,8 +118,134 @@ describe('class member Renderer, ', function () { }); }); - describe('when parsing text to build field display string', function () { - it('should handle simple field declaration', function () { + describe('when parsing method visibility', function () { + it('should correctly handle public', function () { + const str = '+foo()'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('+foo()'); + expect(actual.cssStyle).toBe(''); + }); + + it('should correctly handle private', function () { + const str = '-foo()'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('-foo()'); + expect(actual.cssStyle).toBe(''); + }); + + it('should correctly handle protected', function () { + const str = '#foo()'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('#foo()'); + expect(actual.cssStyle).toBe(''); + }); + + it('should correctly handle package/internal', function () { + const str = '~foo()'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('~foo()'); + expect(actual.cssStyle).toBe(''); + }); + }); + + describe('when parsing method classifier', function () { + it('should handle abstract method', function () { + const str = 'foo()*'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo()'); + expect(actual.cssStyle).toBe('font-style:italic;'); + }); + + it('should handle abstract method with return type', function () { + const str = 'foo(name: String) int*'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(name: String) : int'); + expect(actual.cssStyle).toBe('font-style:italic;'); + }); + + it('should handle abstract method classifier after parenthesis with return type', function () { + const str = 'foo(name: String)* int'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(name: String) : int'); + expect(actual.cssStyle).toBe('font-style:italic;'); + }); + + it('should handle static method classifier', function () { + const str = 'foo()$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo()'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle static method classifier with return type', function () { + const str = 'foo(name: String) int$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(name: String) : int'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle static method classifier with colon and return type', function () { + const str = 'foo(name: String): int$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(name: String) : int'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle static method classifier after parenthesis with return type', function () { + const str = 'foo(name: String)$ int'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo(name: String) : int'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should ignore unknown character for classifier', function () { + const str = 'foo()!'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo()'); + expect(actual.cssStyle).toBe(''); + }); + }); +}); + +describe('given a string representing class member, ', function () { + describe('when parsing member declaration', function () { + it('should handle simple field', function () { + const str = 'id'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('id'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle field with type', function () { + const str = 'int id'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('int id'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle field with type (name first)', function () { + const str = 'id: int'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('id: int'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle array field', function () { const str = 'int[] ids'; let actual = svgDraw.parseMember(str); @@ -156,7 +253,15 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle field declaration with generic type', function () { + it('should handle array field (name first)', function () { + const str = 'ids: int[]'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('ids: int[]'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle field with generic type', function () { const str = 'List~int~ ids'; let actual = svgDraw.parseMember(str); @@ -164,12 +269,62 @@ describe('class member Renderer, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should handle static field classifier', function () { + it('should handle field with generic type (name first)', function () { + const str = 'ids: List~int~'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('ids: List'); + expect(actual.cssStyle).toBe(''); + }); + }); + + describe('when parsing classifiers', function () { + it('should handle static field', function () { const str = 'String foo$'; let actual = svgDraw.parseMember(str); expect(actual.displayText).toBe('String foo'); expect(actual.cssStyle).toBe('text-decoration:underline;'); }); + + it('should handle static field (name first)', function () { + const str = 'foo: String$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo: String'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle static field with generic type', function () { + const str = 'List~String~ foo$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('List foo'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle static field with generic type (name first)', function () { + const str = 'foo: List~String~$'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('foo: List'); + expect(actual.cssStyle).toBe('text-decoration:underline;'); + }); + + it('should handle field with nested generic type', function () { + const str = 'List~List~int~~ idLists'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('List> idLists'); + expect(actual.cssStyle).toBe(''); + }); + + it('should handle field with nested generic type (name first)', function () { + const str = 'idLists: List~List~int~~'; + let actual = svgDraw.parseMember(str); + + expect(actual.displayText).toBe('idLists: List>'); + expect(actual.cssStyle).toBe(''); + }); }); }); diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 3b72e8718..369d84f21 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -1,6 +1,8 @@ import DOMPurify from 'dompurify'; import { MermaidConfig } from '../../config.type.js'; +export const lineBreakRegex = //gi; + /** * Gets the rows of lines in a string * @@ -65,8 +67,6 @@ export const sanitizeTextOrArray = ( return a.flat().map((x: string) => sanitizeText(x, config)); }; -export const lineBreakRegex = //gi; - /** * Whether or not a text has any line breaks * diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 9d3766590..5dee13918 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -74,7 +74,7 @@ classDiagram Vehicle <|-- Car ``` -Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores. +Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-). ### Class labels @@ -171,12 +171,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function - `#` Protected - `~` Package/Internal -> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`: +> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type: > -> - `*` Abstract e.g.: `someAbstractMethod()*` -> - `$` Static e.g.: `someStaticMethod()$` +> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*` +> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$` -> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name: +> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end: > > - `$` Static e.g.: `String someField$` From 62870597d6a77322ae6a9a2db80be8b65b05ef9b Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 17 Apr 2023 06:51:08 -0700 Subject: [PATCH 18/64] Fixed failing tests --- .../mermaid/src/diagrams/class/classDiagram.spec.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 8d8eca0f1..67012b41e 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -7,6 +7,7 @@ const spyOn = vi.spyOn; describe('given a basic class diagram, ', function () { describe('when parsing class definition', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); it('should handle accTitle and accDescr', function () { @@ -267,6 +268,7 @@ class C13["With Città foreign language"] describe('when parsing class defined in brackets', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); @@ -358,6 +360,7 @@ class C13["With Città foreign language"] describe('when parsing comments', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); @@ -447,6 +450,7 @@ foo() describe('when parsing click statements', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); it('should handle href link', function () { @@ -557,8 +561,8 @@ foo() describe('when parsing annotations', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; - parser.yy.clear(); }); it('should handle class annotations', function () { @@ -621,8 +625,8 @@ foo() describe('given a class diagram with members and methods ', function () { describe('when parsing members', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; - parser.yy.clear(); }); it('should handle simple member declaration', function () { @@ -657,6 +661,7 @@ describe('given a class diagram with members and methods ', function () { describe('when parsing method definition', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); @@ -739,6 +744,7 @@ describe('given a class diagram with members and methods ', function () { describe('given a class diagram with generics, ', function () { describe('when parsing valid generic classes', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); @@ -805,6 +811,7 @@ describe('given a class diagram with generics, ', function () { describe('when parsing invalid generic classes', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); @@ -853,6 +860,7 @@ describe('given a class diagram with generics, ', function () { describe('given a class diagram with relationships, ', function () { describe('when parsing basic relationships', function () { beforeEach(function () { + classDb.clear(); parser.yy = classDb; }); From 642bc1a7391889168e46e3c2b134d7d7a7b24ca4 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 17 Apr 2023 07:24:27 -0700 Subject: [PATCH 19/64] update tests for package visibility --- cypress/integration/rendering/classDiagram.spec.js | 2 +- .../mermaid/src/diagrams/class/classDiagram.spec.ts | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index cda455f0e..427b4cf0b 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -286,7 +286,7 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('15: should render a simple class diagram with css classes applied two multiple classes', () => { + it('15: should render a simple class diagram with css classes applied to multiple classes', () => { imgSnapshotTest( ` classDiagram diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 67012b41e..f2492e5cc 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -647,9 +647,18 @@ describe('given a class diagram with members and methods ', function () { 'class actual\n' + 'actual : -int privateMember\n' + 'actual : +int publicMember\n' + - 'actual : #int protectedMember'; + 'actual : #int protectedMember\n' + + 'actual : ~int privatePackage'; parser.parse(str); + + const actual = parser.yy.getClass('actual'); + expect(actual.members.length).toBe(4); + expect(actual.methods.length).toBe(0); + expect(actual.members[0]).toBe('-int privateMember'); + expect(actual.members[1]).toBe('+int publicMember'); + expect(actual.members[2]).toBe('#int protectedMember'); + expect(actual.members[3]).toBe('~int privatePackage'); }); it('should handle generic types', function () { From 28155b0e0a30aef8898985e2967f982a7fa99e42 Mon Sep 17 00:00:00 2001 From: Will-Low <26700668+Will-Low@users.noreply.github.com> Date: Mon, 17 Apr 2023 07:29:17 -0700 Subject: [PATCH 20/64] Removing redundant code block --- docs/syntax/classDiagram.md | 16 ---------------- packages/mermaid/src/docs/syntax/classDiagram.md | 8 -------- 2 files changed, 24 deletions(-) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 9c8f8aadb..15ae26444 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -624,22 +624,6 @@ classDiagram } ``` -```mermaid-example -classDiagram - note "This is a general note" - note for MyClass "This is a note for a class" - class MyClass{ - } -``` - -```mermaid -classDiagram - note "This is a general note" - note for MyClass "This is a note for a class" - class MyClass{ - } -``` - _URL Link:_ ```mermaid-example diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index f824b0bfc..d019774b2 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -407,14 +407,6 @@ It is possible to add notes on the diagram using `note "line1\nline2"`. A note c ### Examples -```mmd -classDiagram - note "This is a general note" - note for MyClass "This is a note for a class" - class MyClass{ - } -``` - ```mermaid classDiagram note "This is a general note" From 6ccdc2bd01d1459e47eba4b560116c2e048f0ac2 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Mon, 17 Apr 2023 19:33:51 +0200 Subject: [PATCH 21/64] Fix for async handling flowchart-elk --- cypress/platform/knsv2.html | 4 ++-- .../mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index e8e9b55a6..1b1ccd685 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -393,9 +393,9 @@ mindmap