From 533830172c9b273c77488d63253c60dd1dac8b27 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Tue, 7 Mar 2023 09:38:51 +0530 Subject: [PATCH 01/47] 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 5693f6360333811a0ca8085e698bea8203946704 Mon Sep 17 00:00:00 2001 From: Lishid Date: Tue, 4 Apr 2023 11:22:19 -0400 Subject: [PATCH 02/47] 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 03/47] 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 04/47] 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 05/47] 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 06/47] 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 07/47] 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 08/47] 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 09/47] 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 10/47] 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 11/47] 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 12/47] 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 13/47] 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 14/47] 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