From 0aa09bfca597a5302f2b35a9ba91fe0403da928d Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 30 May 2023 10:43:44 -0700 Subject: [PATCH 01/56] Create new type for member handling --- cSpell.json | 3 + .../rendering/classDiagram-v2.spec.js | 25 +- .../class/classDiagram-styles.spec.js | 4 +- .../src/diagrams/class/classDiagram-v2.ts | 2 +- .../src/diagrams/class/classDiagram.spec.ts | 284 +++++++++--------- .../src/diagrams/class/classDiagram.ts | 2 +- .../src/diagrams/class/classParser.spec.ts | 12 +- .../class/{classDb.ts => classParser.ts} | 1 + .../src/diagrams/class/classRenderer-v2.ts | 14 +- .../src/diagrams/class/classTypes.spec.ts | 211 +++++++++++++ .../mermaid/src/diagrams/class/classTypes.ts | 86 ++++++ .../diagrams/class/parser/classDiagram.jison | 34 +-- .../mermaid/src/diagrams/class/svgDraw.js | 109 ++++--- .../src/diagrams/class/svgDraw.spec.js | 41 ++- .../mermaid/src/diagrams/common/common.ts | 6 +- 15 files changed, 579 insertions(+), 255 deletions(-) rename packages/mermaid/src/diagrams/class/{classDb.ts => classParser.ts} (99%) create mode 100644 packages/mermaid/src/diagrams/class/classTypes.spec.ts diff --git a/cSpell.json b/cSpell.json index 154d01a99b..e72a7bb2bd 100644 --- a/cSpell.json +++ b/cSpell.json @@ -37,7 +37,10 @@ "docsy", "doku", "dompurify", + "dont", + "doublecircle", "edgechromium", + "elems", "elkjs", "faber", "flatmap", diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 2e7a1cbd72..40d2f5cd78 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -386,12 +386,11 @@ describe('Class diagram V2', () => { { logLevel: 1, flowchart: { htmlLabels: false } } ); }); - - it('18: should handle the direction statement with LR', () => { + it('17a: should handle the direction statement with BT', () => { imgSnapshotTest( ` classDiagram - direction LR + direction BT class Student { -idCard : IdCard } @@ -410,11 +409,11 @@ describe('Class diagram V2', () => { { logLevel: 1, flowchart: { htmlLabels: false } } ); }); - it('17a: should handle the direction statement with BT', () => { + it('17b: should handle the direction statement with RL', () => { imgSnapshotTest( ` classDiagram - direction BT + direction RL class Student { -idCard : IdCard } @@ -433,11 +432,12 @@ describe('Class diagram V2', () => { { logLevel: 1, flowchart: { htmlLabels: false } } ); }); - it('17b: should handle the direction statement with RL', () => { + + it('18a: should handle the direction statement with LR', () => { imgSnapshotTest( ` classDiagram - direction RL + direction LR class Student { -idCard : IdCard } @@ -457,7 +457,7 @@ describe('Class diagram V2', () => { ); }); - it('18: should render a simple class diagram with notes', () => { + it('18b: should render a simple class diagram with notes', () => { imgSnapshotTest( ` classDiagram-v2 @@ -562,4 +562,13 @@ class C13["With Città foreign language"] ` ); }); + it('should render a simple class diagram with no members', () => { + imgSnapshotTest( + ` + classDiagram-v2 + class Class10 + `, + { logLevel: 1, flowchart: { htmlLabels: false } } + ); + }); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js b/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js index a693fbbeab..fe01854b0f 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js +++ b/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js @@ -1,10 +1,10 @@ import { parser } from './parser/classDiagram.jison'; -import classDb from './classDb.js'; +import classParser from './classParser.js'; describe('class diagram, ', function () { describe('when parsing data from a classDiagram it', function () { beforeEach(function () { - parser.yy = classDb; + parser.yy = classParser; parser.yy.clear(); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts index 5b952627cf..c40d36c535 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts @@ -1,7 +1,7 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: TODO Fix ts errors import parser from './parser/classDiagram.jison'; -import db from './classDb.js'; +import db from './classParser.js'; import styles from './styles.js'; import renderer from './classRenderer-v2.js'; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index a43ed2fcda..9da413b6a6 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1,14 +1,14 @@ // @ts-expect-error Jison doesn't export types import { parser } from './parser/classDiagram.jison'; -import classDb from './classDb.js'; +import classParser from './classParser.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; describe('given a basic class diagram, ', function () { describe('when parsing class definition', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle accTitle and accDescr', function () { const str = `classDiagram @@ -54,7 +54,7 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class Ca-r'; parser.parse(str); - const actual = classDb.getClass('Ca-r'); + const actual = classParser.getClass('Ca-r'); expect(actual.label).toBe('Ca-r'); }); @@ -102,7 +102,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); }); @@ -114,9 +114,9 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class 2 with chars @?'); }); @@ -124,7 +124,7 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1'; parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0]).toBe('member1'); @@ -139,7 +139,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0]).toBe('int member1'); @@ -152,7 +152,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass'); }); @@ -166,7 +166,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members[0]).toBe('int member1'); expect(c1.cssClasses[0]).toBe('styleClass'); @@ -182,11 +182,11 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Long long long long long long long long long long label'); expect(c2.cssClasses[0]).toBe('styleClass'); }); @@ -199,11 +199,11 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass1'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class 2 !@#$%^&*() label'); expect(c2.cssClasses[0]).toBe('styleClass2'); }); @@ -214,13 +214,13 @@ class C1["Class with text label"] class C2["Class with text label"] class C3["Class with text label"]`); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class with text label'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class with text label'); - const c3 = classDb.getClass('C3'); + const c3 = classParser.getClass('C3'); expect(c3.label).toBe('Class with text label'); }); @@ -240,19 +240,19 @@ class C11["With ' single quote"] class C12["With ~!@#$%^&*()_+=-/?"] class C13["With Città foreign language"] `); - expect(classDb.getClass('C1').label).toBe('OneWord'); - expect(classDb.getClass('C2').label).toBe('With, Comma'); - expect(classDb.getClass('C3').label).toBe('With (Brackets)'); - expect(classDb.getClass('C4').label).toBe('With [Brackets]'); - expect(classDb.getClass('C5').label).toBe('With {Brackets}'); - expect(classDb.getClass('C6').label).toBe(' '); - expect(classDb.getClass('C7').label).toBe('With 1 number'); - expect(classDb.getClass('C8').label).toBe('With . period...'); - expect(classDb.getClass('C9').label).toBe('With - dash'); - expect(classDb.getClass('C10').label).toBe('With _ underscore'); - expect(classDb.getClass('C11').label).toBe("With ' single quote"); - expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); - expect(classDb.getClass('C13').label).toBe('With Città foreign language'); + expect(classParser.getClass('C1').label).toBe('OneWord'); + expect(classParser.getClass('C2').label).toBe('With, Comma'); + expect(classParser.getClass('C3').label).toBe('With (Brackets)'); + expect(classParser.getClass('C4').label).toBe('With [Brackets]'); + expect(classParser.getClass('C5').label).toBe('With {Brackets}'); + expect(classParser.getClass('C6').label).toBe(' '); + expect(classParser.getClass('C7').label).toBe('With 1 number'); + expect(classParser.getClass('C8').label).toBe('With . period...'); + expect(classParser.getClass('C9').label).toBe('With - dash'); + expect(classParser.getClass('C10').label).toBe('With _ underscore'); + expect(classParser.getClass('C11').label).toBe("With ' single quote"); + expect(classParser.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); + expect(classParser.getClass('C13').label).toBe('With Città foreign language'); }); it('should handle "note for"', function () { @@ -268,8 +268,8 @@ class C13["With Città foreign language"] describe('when parsing class defined in brackets', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle member definitions', function () { @@ -334,7 +334,7 @@ class C13["With Città foreign language"] const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}'; parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0]).toBe('+member1'); @@ -349,7 +349,7 @@ class C13["With Città foreign language"] '}'; parser.parse(str); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0]).toBe('+member1'); @@ -360,8 +360,8 @@ class C13["With Città foreign language"] describe('when parsing comments', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle comments at the start', function () { @@ -450,16 +450,16 @@ foo() describe('when parsing click statements', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle href link', function () { - spyOn(classDb, 'setLink'); + spyOn(classParser, 'setLink'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" '; parser.parse(str); - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); const actual = parser.yy.getClass('Class1'); expect(actual.link).toBe('google.com'); @@ -467,14 +467,14 @@ foo() }); it('should handle href link with tooltip', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setLink'); + spyOn(classParser, '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'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); const actual = parser.yy.getClass('Class1'); expect(actual.link).toBe('google.com'); @@ -483,8 +483,8 @@ foo() }); it('should handle href link with tooltip and target', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setLink'); + spyOn(classParser, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -492,8 +492,8 @@ foo() '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'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); const actual = parser.yy.getClass('Class1'); expect(actual.link).toBe('google.com'); @@ -502,30 +502,30 @@ foo() }); it('should handle function call', function () { - spyOn(classDb, 'setClickEvent'); + spyOn(classParser, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 call functionCall() '; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should handle function call with tooltip', function () { - spyOn(classDb, 'setClickEvent'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setClickEvent'); + spyOn(classParser, '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'); + expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); }); it('should handle function call with an arbitrary number of args', function () { - spyOn(classDb, 'setClickEvent'); + spyOn(classParser, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -533,7 +533,7 @@ foo() 'click Class1 call functionCall(test, test1, test2)'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith( + expect(classParser.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', 'test, test1, test2' @@ -541,8 +541,8 @@ foo() }); it('should handle function call with an arbitrary number of args and tooltip', function () { - spyOn(classDb, 'setClickEvent'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setClickEvent'); + spyOn(classParser, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -550,19 +550,19 @@ foo() 'click Class1 call functionCall("test0", test1, test2) "A Tooltip"'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith( + expect(classParser.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', '"test0", test1, test2' ); - expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); }); }); describe('when parsing annotations', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle class annotations', function () { @@ -625,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; + classParser.clear(); + parser.yy = classParser; }); it('should handle simple member declaration', function () { @@ -670,8 +670,8 @@ describe('given a class diagram with members and methods ', function () { describe('when parsing method definition', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle method definition', function () { @@ -753,8 +753,8 @@ 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; + classParser.clear(); + parser.yy = classParser; }); it('should handle generic class', function () { @@ -851,8 +851,8 @@ foo() describe('when parsing invalid generic classes', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () { @@ -900,8 +900,8 @@ foo() describe('given a class diagram with relationships, ', function () { describe('when parsing basic relationships', function () { beforeEach(function () { - classDb.clear(); - parser.yy = classDb; + classParser.clear(); + parser.yy = classParser; }); it('should handle all basic relationships', function () { @@ -938,9 +938,9 @@ describe('given a class diagram with relationships, ', function () { 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.type1).toBe(classParser.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle relationships with labels', function () { @@ -963,9 +963,9 @@ describe('given a class diagram with relationships, ', function () { 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.type1).toBe(classParser.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle relation definition of different types and directions', function () { @@ -1010,9 +1010,9 @@ describe('given a class diagram with relationships, ', function () { 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.type1).toBe(classParser.relationType.AGGREGATION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); + expect(relations[0].relation.lineType).toBe(classParser.lineType.DOTTED_LINE); }); it('should handle relation definitions COMPOSITION on both sides', function () { @@ -1024,9 +1024,9 @@ describe('given a class diagram with relationships, ', function () { 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); + expect(relations[0].relation.type1).toBe(classParser.relationType.COMPOSITION); + expect(relations[0].relation.type2).toBe(classParser.relationType.COMPOSITION); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle relation definitions with no types', function () { @@ -1040,7 +1040,7 @@ describe('given a class diagram with relationships, ', function () { 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); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle relation definitions with type only on right side', function () { @@ -1053,8 +1053,8 @@ describe('given a class diagram with relationships, ', function () { 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); + expect(relations[0].relation.type2).toBe(classParser.relationType.EXTENSION); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle multiple classes and relation definitions', function () { @@ -1075,12 +1075,12 @@ describe('given a class diagram with relationships, ', function () { expect(relations.length).toBe(5); - expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); + expect(relations[0].relation.type1).toBe(classParser.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classParser.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); + expect(relations[3].relation.lineType).toBe(classParser.lineType.DOTTED_LINE); }); it('should handle generic class with relation definitions', function () { @@ -1093,9 +1093,9 @@ describe('given a class diagram with relationships, ', function () { 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.type1).toBe(classParser.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); }); it('should handle class annotations', function () { @@ -1254,8 +1254,8 @@ describe('given a class diagram with relationships, ', function () { }); it('should associate click and href link with tooltip and target appropriately', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setLink'); + spyOn(classParser, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1263,12 +1263,12 @@ describe('given a class diagram with relationships, ', function () { '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'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); it('should associate click and href link appropriately', function () { - spyOn(classDb, 'setLink'); + spyOn(classParser, 'setLink'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1276,11 +1276,11 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 href "google.com"'; parser.parse(str); - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); }); it('should associate click and href link with target appropriately', function () { - spyOn(classDb, 'setLink'); + spyOn(classParser, 'setLink'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1288,12 +1288,12 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 href "google.com" _self'; parser.parse(str); - expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); }); it('should associate link appropriately', function () { - spyOn(classDb, 'setLink'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setLink'); + spyOn(classParser, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1301,12 +1301,12 @@ describe('given a class diagram with relationships, ', function () { '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'); + expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); it('should associate callback appropriately', function () { - spyOn(classDb, 'setClickEvent'); + spyOn(classParser, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1314,11 +1314,11 @@ describe('given a class diagram with relationships, ', function () { 'callback Class1 "functionCall"'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should associate click and call callback appropriately', function () { - spyOn(classDb, 'setClickEvent'); + spyOn(classParser, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1326,11 +1326,11 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall()'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should associate callback appropriately with an arbitrary number of args', function () { - spyOn(classDb, 'setClickEvent'); + spyOn(classParser, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1338,7 +1338,7 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall("test0", test1, test2)'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith( + expect(classParser.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', '"test0", test1, test2' @@ -1346,8 +1346,8 @@ describe('given a class diagram with relationships, ', function () { }); it('should associate callback with tooltip', function () { - spyOn(classDb, 'setClickEvent'); - spyOn(classDb, 'setTooltip'); + spyOn(classParser, 'setClickEvent'); + spyOn(classParser, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1355,8 +1355,8 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall() "A tooltip"'; parser.parse(str); - expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); it('should add classes namespaces', function () { @@ -1381,7 +1381,7 @@ class Class2 describe('when parsing classDiagram with text labels', () => { beforeEach(function () { - parser.yy = classDb; + parser.yy = classParser; parser.yy.clear(); }); @@ -1390,9 +1390,9 @@ class Class2 class C1["Class 1 with text label"] C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1402,9 +1402,9 @@ class Class2 class C2["Class 2 with chars @?"] C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class 2 with chars @?'); }); @@ -1415,12 +1415,12 @@ class Class2 } C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.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'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1432,14 +1432,14 @@ class Class2 } C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.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'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1451,7 +1451,7 @@ class C1["Class 1 with text label"]:::styleClass { C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses.length).toBe(1); expect(c1.members[0]).toBe('+member1'); @@ -1467,7 +1467,7 @@ C1 --> C2 cssClass "C1" styleClass `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses.length).toBe(1); expect(c1.members[0]).toBe('+member1'); @@ -1484,12 +1484,12 @@ C1 --> C2 cssClass "C1,C2" styleClass `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.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'); + const c2 = classParser.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'); @@ -1504,12 +1504,12 @@ class C2["Class 2 !@#$%^&*() label"]:::styleClass2 C1 --> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.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'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class 2 !@#$%^&*() label'); expect(c2.cssClasses.length).toBe(1); expect(c2.cssClasses[0]).toBe('styleClass2'); @@ -1524,13 +1524,13 @@ C1 --> C2 C3 ..> C2 `); - const c1 = classDb.getClass('C1'); + const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class with text label'); - const c2 = classDb.getClass('C2'); + const c2 = classParser.getClass('C2'); expect(c2.label).toBe('Class with text label'); - const c3 = classDb.getClass('C3'); + const c3 = classParser.getClass('C3'); expect(c3.label).toBe('Class with text label'); }); @@ -1550,19 +1550,19 @@ class C11["With ' single quote"] class C12["With ~!@#$%^&*()_+=-/?"] class C13["With Città foreign language"] `); - expect(classDb.getClass('C1').label).toBe('OneWord'); - expect(classDb.getClass('C2').label).toBe('With, Comma'); - expect(classDb.getClass('C3').label).toBe('With (Brackets)'); - expect(classDb.getClass('C4').label).toBe('With [Brackets]'); - expect(classDb.getClass('C5').label).toBe('With {Brackets}'); - expect(classDb.getClass('C6').label).toBe(' '); - expect(classDb.getClass('C7').label).toBe('With 1 number'); - expect(classDb.getClass('C8').label).toBe('With . period...'); - expect(classDb.getClass('C9').label).toBe('With - dash'); - expect(classDb.getClass('C10').label).toBe('With _ underscore'); - expect(classDb.getClass('C11').label).toBe("With ' single quote"); - expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); - expect(classDb.getClass('C13').label).toBe('With Città foreign language'); + expect(classParser.getClass('C1').label).toBe('OneWord'); + expect(classParser.getClass('C2').label).toBe('With, Comma'); + expect(classParser.getClass('C3').label).toBe('With (Brackets)'); + expect(classParser.getClass('C4').label).toBe('With [Brackets]'); + expect(classParser.getClass('C5').label).toBe('With {Brackets}'); + expect(classParser.getClass('C6').label).toBe(' '); + expect(classParser.getClass('C7').label).toBe('With 1 number'); + expect(classParser.getClass('C8').label).toBe('With . period...'); + expect(classParser.getClass('C9').label).toBe('With - dash'); + expect(classParser.getClass('C10').label).toBe('With _ underscore'); + expect(classParser.getClass('C11').label).toBe("With ' single quote"); + expect(classParser.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); + expect(classParser.getClass('C13').label).toBe('With Città foreign language'); }); }); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram.ts b/packages/mermaid/src/diagrams/class/classDiagram.ts index 0d2a246b4e..f9ae8c7090 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.ts @@ -1,7 +1,7 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: TODO Fix ts errors import parser from './parser/classDiagram.jison'; -import db from './classDb.js'; +import db from './classParser.js'; import styles from './styles.js'; import renderer from './classRenderer.js'; diff --git a/packages/mermaid/src/diagrams/class/classParser.spec.ts b/packages/mermaid/src/diagrams/class/classParser.spec.ts index c479b82721..ed402c28ff 100644 --- a/packages/mermaid/src/diagrams/class/classParser.spec.ts +++ b/packages/mermaid/src/diagrams/class/classParser.spec.ts @@ -1,5 +1,5 @@ import { setConfig } from '../../config.js'; -import classDB from './classDb.js'; +import classParser from './classParser.js'; // @ts-ignore - no types in jison import classDiagram from './parser/classDiagram.jison'; @@ -9,7 +9,7 @@ setConfig({ describe('when parsing class diagram', function () { beforeEach(function () { - classDiagram.parser.yy = classDB; + classDiagram.parser.yy = classParser; classDiagram.parser.yy.clear(); }); @@ -30,8 +30,8 @@ describe('when parsing class diagram', function () { Student "1" --o "1" IdCard : carries Student "1" --o "1" Bike : rides`); - expect(Object.keys(classDB.getClasses()).length).toBe(3); - expect(classDB.getClasses().Student).toMatchInlineSnapshot(` + expect(Object.keys(classParser.getClasses()).length).toBe(3); + expect(classParser.getClasses().Student).toMatchInlineSnapshot(` { "annotations": [], "cssClasses": [], @@ -45,8 +45,8 @@ describe('when parsing class diagram', function () { "type": "", } `); - expect(classDB.getRelations().length).toBe(2); - expect(classDB.getRelations()).toMatchInlineSnapshot(` + expect(classParser.getRelations().length).toBe(2); + expect(classParser.getRelations()).toMatchInlineSnapshot(` [ { "id1": "Student", diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classParser.ts similarity index 99% rename from packages/mermaid/src/diagrams/class/classDb.ts rename to packages/mermaid/src/diagrams/class/classParser.ts index d9e17db548..aa2d02975e 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classParser.ts @@ -255,6 +255,7 @@ export const getTooltip = function (id: string, namespace?: string) { return classes[id].tooltip; }; + /** * Called by parser when a link is found. Adds the URL to the vertex data. * diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index 3520022427..e648854d35 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -92,7 +92,6 @@ export const addClasses = function ( log.info('keys:', keys); log.info(classes); - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition keys.forEach(function (id) { const vertex = classes[id]; @@ -157,24 +156,17 @@ export const addNotes = function ( ) { log.info(notes); - // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition notes.forEach(function (note, i) { const vertex = note; - /** - * Variable for storing the classes for the vertex - * - */ const cssNoteStr = ''; const styles = { labelStyle: '', style: '' }; - // Use vertex id as text in the box if no text is provided by the graph definition const vertexText = vertex.text; const radius = 0; const shape = 'note'; - // Add the node const node = { labelStyle: styles.labelStyle, shape: shape, @@ -302,7 +294,7 @@ export const setConf = function (cnf: any) { }; /** - * Draws a flowchart in the tag with id: id based on the graph definition in text. + * Draws a class diagram in the tag with id: id based on the definition in text. * * @param text - * @param id - @@ -353,9 +345,7 @@ export const draw = async function (text: string, id: string, _version: string, } const root = securityLevel === 'sandbox' - ? // @ts-ignore Ignore type error for now - - select(sandboxElement.nodes()[0].contentDocument.body) + ? select(sandboxElement.nodes()[0].contentDocument.body) : select('body'); // @ts-ignore Ignore type error for now const svg = root.select(`[id="${id}"]`); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts new file mode 100644 index 0000000000..b5ff157c72 --- /dev/null +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -0,0 +1,211 @@ +import { ClassMember } from './classTypes.js'; +import { vi, describe, it, expect } from 'vitest'; +const spyOn = vi.spyOn; + +describe('given text representing member declaration, ', function () { + describe('when text is a method with no parameters', function () { + it('should parse simple method', function () { + const str = `getTime()`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse public visibiity', function () { + const str = `+getTime()`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); + + it('should parse private visibiity', function () { + const str = `-getTime()`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); + + it('should parse protected visibiity', function () { + const str = `#getTime()`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); + + it('should parse internal visibiity', function () { + const str = `~getTime()`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); + + it('should return correct css for static classifier', function () { + const str = `getTime()$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for abstrtact', function () { + const str = `getTime()*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + }); + + describe('when text is a method with parameters', function () { + it('should parse method with parameter type, as provided', function () { + const str = `getTime(String)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse method with parameter type and name, as provided', function () { + const str = `getTime(String time)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse method with parameters, as provided', function () { + const str = `getTime(String time, date Date)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should return correct css for static method with parameter type, as provided', function () { + const str = `getTime(String)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for static method with parameter type and name, as provided', function () { + const str = `getTime(String time)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for static method with parameters, as provided', function () { + const str = `getTime(String time, date Date)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for abstract method with parameter type, as provided', function () { + const str = `getTime(String)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + + it('should return correct css for abstract method with parameter type and name, as provided', function () { + const str = `getTime(String time)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + + it('should return correct css for abstract method with parameters, as provided', function () { + const str = `getTime(String time, date Date)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + }); + + describe('when text is a method with return type', function () { + it('should parse simple method with no parameter', function () { + const str = `getTime() String`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse method with parameter type, as provided', function () { + const str = `getTime(String) String`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse method with parameter type and name, as provided', function () { + const str = `getTime(String time) String`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should parse method with parameters, as provided', function () { + const str = `getTime(String time, date Date) String`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should return correct css for static method with no parameter', function () { + const str = `getTime() String$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() String'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for static method with parameter type and name, as provided', function () { + const str = `getTime(String time) String$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for static method with parameters, as provided', function () { + const str = `getTime(String time, date Date)$ String`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTime(String time, date Date) String' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + }); + + it('should return correct css for abstract method with parameter type, as provided', function () { + const str = `getTime(String) String*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String) String'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + + it('should return correct css for abstract method with parameter type and name, as provided', function () { + const str = `getTime(String time) String*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + + it('should return correct css for abstract method with parameters, as provided', function () { + const str = `getTime(String time, date Date) String*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTime(String time, date Date) String' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index cf6f20f0b1..becf6fe0d3 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -1,3 +1,5 @@ +import { parseGenericTypes } from '../common/common.js'; + export interface ClassNode { id: string; type: string; @@ -13,6 +15,90 @@ export interface ClassNode { tooltip?: string; } +export class ClassMember { + id!: string; + cssStyle!: string; + memberType!: string; + visibility!: string; + classifier!: string; + parameters!: string; + returnType!: string; + + constructor(input: string, memberType: string) { + this.memberType = memberType; + this.parseMember(input); + } + + getDisplayDetails() { + let displayText = this.visibility + parseGenericTypes(this.id); + if (this.memberType === 'method') { + displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')' + ' ' + this.returnType; + } + + displayText = displayText.trim(); + const cssStyle = this.parseClassifier(); + + return { + displayText, + cssStyle, + }; + } + + parseMember(input: string) { + let potentialClassifier = ''; + + if (this.memberType === 'method') { + const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/; + const match = input.match(methodRegEx); + if (match) { + this.visibility = match[1] ? match[1].trim() : ''; + this.id = match[2].trim(); + this.parameters = match[3] ? match[3].trim() : ''; + potentialClassifier = match[4] ? match[4].trim() : ''; + this.returnType = match[5] ? match[5].trim() : ''; + + if (potentialClassifier === '') { + const lastChar = this.returnType.substring(this.returnType.length - 1); + if (lastChar.match(/[$*]/)) { + potentialClassifier = lastChar; + this.returnType = this.returnType.substring(0, this.returnType.length - 1); + } + } + } + } else { + const length = input.length; + const firstChar = input.substring(0, 1); + const lastChar = input.substring(length - 1); + + if (firstChar.match(/[#+~-]/)) { + this.visibility = firstChar; + } + + if (lastChar.match(/[*?]/)) { + potentialClassifier = lastChar; + } + + this.id = input.substring( + this.visibility === '' ? 0 : 1, + potentialClassifier === '' ? length : length - 1 + ); + } + + this.classifier = potentialClassifier; + } + + parseClassifier() { + switch (this.classifier) { + case '*': + return 'font-style:italic;'; + case '$': + return 'text-decoration:underline;'; + default: + return ''; + } + } +} + export interface ClassNote { id: string; class: string; diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison index 7788fcc0c0..c5130194f3 100644 --- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison +++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison @@ -226,21 +226,14 @@ start | statements ; -direction - : direction_tb - { yy.setDirection('TB');} - | direction_bt - { yy.setDirection('BT');} - | direction_rl - { yy.setDirection('RL');} - | direction_lr - { yy.setDirection('LR');} - ; - mermaidDoc : graphConfig ; +graphConfig + : CLASS_DIAGRAM NEWLINE statements EOF + ; + directive : openDirective typeDirective closeDirective NEWLINE | openDirective typeDirective ':' argDirective closeDirective NEWLINE @@ -262,10 +255,6 @@ closeDirective : close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); } ; -graphConfig - : CLASS_DIAGRAM NEWLINE statements EOF - ; - statements : statement | statement NEWLINE @@ -294,7 +283,7 @@ statement | relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); } | namespaceStatement | classStatement - | methodStatement + | memberStatement | annotationStatement | clickStatement | cssClassStatement @@ -341,7 +330,7 @@ members | MEMBER members { $2.push($1);$$=$2;} ; -methodStatement +memberStatement : className {/*console.log('Rel found',$1);*/} | className LABEL {yy.addMember($1,yy.cleanupLabel($2));} | MEMBER {/*console.warn('Member',$1);*/} @@ -360,6 +349,17 @@ noteStatement | NOTE noteText { yy.addNote($2); } ; +direction + : direction_tb + { yy.setDirection('TB');} + | direction_bt + { yy.setDirection('BT');} + | direction_rl + { yy.setDirection('RL');} + | direction_lr + { yy.setDirection('LR');} + ; + relation : relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; } | lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; } diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index e4afe21368..37c8319d23 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -172,7 +172,6 @@ export const drawClass = function (elem, classDef, conf, diagObj) { // add class group const g = elem.append('g').attr('id', diagObj.db.lookUpDomId(id)).attr('class', 'classGroup'); - // add title let title; if (classDef.link) { title = g @@ -209,47 +208,56 @@ export const drawClass = function (elem, classDef, conf, diagObj) { } const titleHeight = title.node().getBBox().height; + let membersLine; + let membersBox; + let methodsLine; + + // don't draw box if no members + if (classDef.members.length > 0) { + membersLine = g + .append('line') // text label for the x axis + .attr('x1', 0) + .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) + .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2); + + const members = g + .append('text') // text label for the x axis + .attr('x', conf.padding) + .attr('y', titleHeight + conf.dividerMargin + conf.textHeight) + .attr('fill', 'white') + .attr('class', 'classText'); + + isFirst = true; + classDef.members.forEach(function (member) { + addTspan(members, member, isFirst, conf); + isFirst = false; + }); + + membersBox = members.node().getBBox(); + } - const membersLine = g - .append('line') // text label for the x axis - .attr('x1', 0) - .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) - .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2); - - const members = g - .append('text') // text label for the x axis - .attr('x', conf.padding) - .attr('y', titleHeight + conf.dividerMargin + conf.textHeight) - .attr('fill', 'white') - .attr('class', 'classText'); - - isFirst = true; - classDef.members.forEach(function (member) { - addTspan(members, member, isFirst, conf); - isFirst = false; - }); - - const membersBox = members.node().getBBox(); - - const methodsLine = g - .append('line') // text label for the x axis - .attr('x1', 0) - .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) - .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height); - - const methods = g - .append('text') // text label for the x axis - .attr('x', conf.padding) - .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight) - .attr('fill', 'white') - .attr('class', 'classText'); - - isFirst = true; - - classDef.methods.forEach(function (method) { - addTspan(methods, method, isFirst, conf); - isFirst = false; - }); + // don't draw box if no methods + if (classDef.methods.length > 0) { + methodsLine = g + .append('line') // text label for the x axis + .attr('x1', 0) + .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) + .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height); + + const methods = g + .append('text') // text label for the x axis + .attr('x', conf.padding) + .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight) + .attr('fill', 'white') + .attr('class', 'classText'); + + isFirst = true; + + classDef.methods.forEach(function (method) { + addTspan(methods, method, isFirst, conf); + isFirst = false; + }); + } const classBox = g.node().getBBox(); var cssClassStr = ' '; @@ -278,8 +286,12 @@ export const drawClass = function (elem, classDef, conf, diagObj) { title.insert('title').text(classDef.tooltip); } - membersLine.attr('x2', rectWidth); - methodsLine.attr('x2', rectWidth); + if (membersLine) { + membersLine.attr('x2', rectWidth); + } + if (methodsLine) { + methodsLine.attr('x2', rectWidth); + } classInfo.width = rectWidth; classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin; @@ -366,20 +378,20 @@ export const parseMember = function (text) { let returnType = ''; let visibility = ''; - let firstChar = text.substring(0, 1); - let lastChar = text.substring(text.length - 1, text.length); + const firstChar = text.substring(0, 1); + const lastChar = text.substring(text.length - 1, text.length); if (firstChar.match(/[#+~-]/)) { visibility = firstChar; } - let noClassifierRe = /[\s\w)~]/; + const noClassifierRe = /[\s\w)~]/; if (!lastChar.match(noClassifierRe)) { cssStyle = parseClassifier(lastChar); } const startIndex = visibility === '' ? 0 : 1; - let endIndex = cssStyle === '' ? text.length : text.length - 1; + const endIndex = cssStyle === '' ? text.length : text.length - 1; text = text.substring(startIndex, endIndex); const methodStart = text.indexOf('('); @@ -387,8 +399,7 @@ export const parseMember = function (text) { const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length; if (isMethod) { - let methodName = text.substring(0, methodStart).trim(); - + const methodName = text.substring(0, methodStart).trim(); const parameters = text.substring(methodStart + 1, methodEnd); displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.spec.js b/packages/mermaid/src/diagrams/class/svgDraw.spec.js index e8ba9f7e1e..6f9b78d27d 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.spec.js @@ -1,6 +1,6 @@ import svgDraw from './svgDraw.js'; -describe('given a string representing class method, ', function () { +describe('given a string representing a class, ', function () { it('should handle class names with generics', function () { const classDef = { id: 'Car', @@ -11,8 +11,18 @@ describe('given a string representing class method, ', function () { let actual = svgDraw.getClassTitleString(classDef); expect(actual).toBe('Car'); }); + it('should handle class names with nested generics', function () { + const classDef = { + id: 'Car', + type: 'T~TT~', + label: 'Car', + }; - describe('when parsing base method declaration', function () { + let actual = svgDraw.getClassTitleString(classDef); + expect(actual).toBe('Car>'); + }); + + describe('when parsing member method', function () { it('should handle simple declaration', function () { const str = 'foo()'; let actual = svgDraw.parseMember(str); @@ -116,10 +126,8 @@ describe('given a string representing class method, ', function () { expect(actual.displayText).toBe('+foo(List> ids) : List>'); expect(actual.cssStyle).toBe('font-style:italic;'); }); - }); - describe('when parsing method visibility', function () { - it('should correctly handle public', function () { + it('should correctly handle public visibility', function () { const str = '+foo()'; let actual = svgDraw.parseMember(str); @@ -127,7 +135,7 @@ describe('given a string representing class method, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should correctly handle private', function () { + it('should correctly handle private visibility', function () { const str = '-foo()'; let actual = svgDraw.parseMember(str); @@ -135,7 +143,7 @@ describe('given a string representing class method, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should correctly handle protected', function () { + it('should correctly handle protected visibility', function () { const str = '#foo()'; let actual = svgDraw.parseMember(str); @@ -143,16 +151,14 @@ describe('given a string representing class method, ', function () { expect(actual.cssStyle).toBe(''); }); - it('should correctly handle package/internal', function () { + it('should correctly handle package/internal visibility', 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); @@ -217,10 +223,8 @@ describe('given a string representing class method, ', function () { expect(actual.cssStyle).toBe(''); }); }); -}); -describe('given a string representing class member, ', function () { - describe('when parsing member declaration', function () { + describe('when parsing member field', function () { it('should handle simple field', function () { const str = 'id'; let actual = svgDraw.parseMember(str); @@ -276,9 +280,7 @@ describe('given a string representing class member, ', function () { 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); @@ -328,3 +330,12 @@ describe('given a string representing class member, ', function () { }); }); }); + +describe('given a string representing class with no members, ', function () { + it('should have no members', function () { + const str = 'class Class10'; + let actual = svgDraw.drawClass(str); + + expect(actual.displayText).toBe(''); + }); +}); diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 243c0cbf25..275e16a8e8 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -178,13 +178,15 @@ export const getMin = function (...values: number[]): number { * @returns The converted string */ export const parseGenericTypes = function (text: string): string { + if (!text) { + return ''; + } + let cleanedText = text; if (text.split('~').length - 1 >= 2) { let newCleanedText = cleanedText; - // use a do...while loop instead of replaceAll to detect recursion - // e.g. Array~Array~T~~ do { cleanedText = newCleanedText; newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>'); From b0b3c7f4103881518f2015be4bbd393ffad4ef17 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 2 Jun 2023 03:08:53 -0700 Subject: [PATCH 02/56] Update and add tests --- .../mermaid/src/diagrams/class/classParser.ts | 4 +- .../src/diagrams/class/classRenderer-v2.ts | 32 +- .../src/diagrams/class/classTypes.spec.ts | 688 ++++++++++++++---- .../mermaid/src/diagrams/class/classTypes.ts | 4 +- .../mermaid/src/diagrams/class/svgDraw.js | 92 +-- .../src/diagrams/class/svgDraw.spec.js | 357 +-------- 6 files changed, 590 insertions(+), 587 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classParser.ts b/packages/mermaid/src/diagrams/class/classParser.ts index aa2d02975e..5c2751dc99 100644 --- a/packages/mermaid/src/diagrams/class/classParser.ts +++ b/packages/mermaid/src/diagrams/class/classParser.ts @@ -114,11 +114,11 @@ export const clear = function () { commonClear(); }; -export const getClass = function (id: string) { +export const getClass = function (id: string): ClassNode { return classes[id]; }; -export const getClasses = function () { +export const getClasses = function (): ClassMap { return classes; }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts index e648854d35..22522d6c7b 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts @@ -93,20 +93,20 @@ export const addClasses = function ( log.info(classes); keys.forEach(function (id) { - const vertex = classes[id]; + const classNode = classes[id]; /** - * Variable for storing the classes for the vertex + * Variable for storing the css classes for the vertex */ let cssClassStr = ''; - if (vertex.cssClasses.length > 0) { - cssClassStr = cssClassStr + ' ' + vertex.cssClasses.join(' '); + if (classNode.cssClasses.length > 0) { + cssClassStr = cssClassStr + ' ' + classNode.cssClasses.join(' '); } const styles = { labelStyle: '', style: '' }; //getStylesFromArray(vertex.styles); // Use vertex id as text in the box if no text is provided by the graph definition - const vertexText = vertex.label ?? vertex.id; + const nodeText = classNode.label ?? classNode.id; const radius = 0; const shape = 'class_box'; @@ -114,26 +114,26 @@ export const addClasses = function ( const node = { labelStyle: styles.labelStyle, shape: shape, - labelText: sanitizeText(vertexText), - classData: vertex, + labelText: sanitizeText(nodeText), + classData: classNode, rx: radius, ry: radius, class: cssClassStr, style: styles.style, - id: vertex.id, - domId: vertex.domId, - tooltip: diagObj.db.getTooltip(vertex.id, parent) || '', - haveCallback: vertex.haveCallback, - link: vertex.link, - width: vertex.type === 'group' ? 500 : undefined, - type: vertex.type, + id: classNode.id, + domId: classNode.domId, + tooltip: diagObj.db.getTooltip(classNode.id, parent) || '', + haveCallback: classNode.haveCallback, + link: classNode.link, + width: classNode.type === 'group' ? 500 : undefined, + type: classNode.type, // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release padding: getConfig().flowchart?.padding ?? getConfig().class?.padding, }; - g.setNode(vertex.id, node); + g.setNode(classNode.id, node); if (parent) { - g.setParent(vertex.id, parent); + g.setParent(classNode.id, parent); } log.info('setNode', node); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index b5ff157c72..cd13cb0eb2 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -2,210 +2,600 @@ import { ClassMember } from './classTypes.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; -describe('given text representing member declaration, ', function () { - describe('when text is a method with no parameters', function () { - it('should parse simple method', function () { - const str = `getTime()`; +const staticCssStyle = 'text-decoration:underline;'; +const abstractCssStyle = 'font-style:italic;'; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); +describe('given text representing a member, ', function () { + describe('when parseMember is called as method', function () { + describe('when method has no parameters', function () { + it('should parse correctly', function () { + const str = `getTime()`; - it('should parse public visibiity', function () { - const str = `+getTime()`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + it('should handle public visibility', function () { + const str = `+getTime()`; - it('should parse private visibiity', function () { - const str = `-getTime()`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + it('should handle private visibility', function () { + const str = `-getTime()`; - it('should parse protected visibiity', function () { - const str = `#getTime()`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + it('should handle protected visibility', function () { + const str = `#getTime()`; - it('should parse internal visibiity', function () { - const str = `~getTime()`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + it('should handle internal visibility', function () { + const str = `~getTime()`; - it('should return correct css for static classifier', function () { - const str = `getTime()$`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + it('should return correct css for static classifier', function () { + const str = `getTime()$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstrtact', function () { - const str = `getTime()*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime()*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); }); - }); - describe('when text is a method with parameters', function () { - it('should parse method with parameter type, as provided', function () { - const str = `getTime(String)`; + describe('when method has single parameter value', function () { + it('should parse correctly', function () { + const str = `getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should parse method with parameter type and name, as provided', function () { - const str = `getTime(String time)`; + it('should handle public visibility', function () { + const str = `+getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); - it('should parse method with parameters, as provided', function () { - const str = `getTime(String time, date Date)`; + it('should handle private visibility', function () { + const str = `-getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); - it('should return correct css for static method with parameter type, as provided', function () { - const str = `getTime(String)$`; + it('should handle protected visibility', function () { + const str = `#getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); - it('should return correct css for static method with parameter type and name, as provided', function () { - const str = `getTime(String time)$`; + it('should handle internal visibility', function () { + const str = `~getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); - it('should return correct css for static method with parameters, as provided', function () { - const str = `getTime(String time, date Date)$`; + it('should return correct css for static classifier', function () { + const str = `getTime(int)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract method with parameter type, as provided', function () { - const str = `getTime(String)*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(int)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); }); - it('should return correct css for abstract method with parameter type and name, as provided', function () { - const str = `getTime(String time)*`; + describe('when method has single parameter type and name (type first)', function () { + it('should parse correctly', function () { + const str = `getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should return correct css for abstract method with parameters, as provided', function () { - const str = `getTime(String time, date Date)*`; + it('should handle public visibility', function () { + const str = `+getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); - }); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); - describe('when text is a method with return type', function () { - it('should parse simple method with no parameter', function () { - const str = `getTime() String`; + it('should handle private visibility', function () { + const str = `-getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); - it('should parse method with parameter type, as provided', function () { - const str = `getTime(String) String`; + it('should handle protected visibility', function () { + const str = `#getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); - it('should parse method with parameter type and name, as provided', function () { - const str = `getTime(String time) String`; + it('should handle internal visibility', function () { + const str = `~getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); - it('should parse method with parameters, as provided', function () { - const str = `getTime(String time, date Date) String`; + it('should return correct css for static classifier', function () { + const str = `getTime(int count)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for static method with no parameter', function () { - const str = `getTime() String$`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(int count)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime() String'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); }); - it('should return correct css for static method with parameter type and name, as provided', function () { - const str = `getTime(String time) String$`; + describe('when method has single parameter type and name (name first)', function () { + it('should parse correctly', function () { + const str = `getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should return correct css for static method with parameters, as provided', function () { - const str = `getTime(String time, date Date)$ String`; + it('should handle public visibility', function () { + const str = `+getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe( - 'getTime(String time, date Date) String' - ); - expect(classMember.getDisplayDetails().cssStyle).toBe('text-decoration:underline;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); - it('should return correct css for abstract method with parameter type, as provided', function () { - const str = `getTime(String) String*`; + it('should handle private visibility', function () { + const str = `-getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String) String'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); + + it('should handle protected visibility', function () { + const str = `#getTime(count int)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); + + it('should handle internal visibility', function () { + const str = `~getTime(count int)`; - it('should return correct css for abstract method with parameter type and name, as provided', function () { - const str = `getTime(String time) String*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + it('should return correct css for static classifier', function () { + const str = `getTime(count int)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTime(count int)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); }); - it('should return correct css for abstract method with parameters, as provided', function () { - const str = `getTime(String time, date Date) String*`; + describe('when method has multiple parameters', function () { + it('should parse correctly', function () { + const str = `getTime(string text, int count)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); + + it('should handle public visibility', function () { + const str = `+getTime(string text, int count)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); + + it('should handle private visibility', function () { + const str = `-getTime(string text, int count)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); + + it('should handle protected visibility', function () { + const str = `#getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe( - 'getTime(String time, date Date) String' - ); - expect(classMember.getDisplayDetails().cssStyle).toBe('font-style:italic;'); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); + + it('should handle internal visibility', function () { + const str = `~getTime(string text, int count)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); + + it('should return correct css for static classifier', function () { + const str = `getTime(string text, int count)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTime(string text, int count)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); }); }); }); + +// it('should return correct css for static method with parameter type, as provided', function () { +// const str = `getTime(String)$`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for static method with parameter type and name, as provided', function () { +// const str = `getTime(String time)$`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for static method with parameters, as provided', function () { +// const str = `getTime(String time, date Date)$`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for abstract method with parameter type, as provided', function () { +// const str = `getTime(String)*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); + +// it('should return correct css for abstract method with parameter type and name, as provided', function () { +// const str = `getTime(String time)*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); + +// it('should return correct css for abstract method with parameters, as provided', function () { +// const str = `getTime(String time, date Date)*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); +// }); + +// describe('when text is a method with return type', function () { +// it('should parse simple method with no parameter', function () { +// const str = `getTime() String`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe(str); +// }); + +// it('should parse method with parameter type, as provided', function () { +// const str = `getTime(String) String`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe(str); +// }); + +// it('should parse method with parameter type and name, as provided', function () { +// const str = `getTime(String time) String`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe(str); +// }); + +// it('should parse method with parameters, as provided', function () { +// const str = `getTime(String time, date Date) String`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe(str); +// }); + +// it('should return correct css for static method with no parameter', function () { +// const str = `getTime() String$`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime() String'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for static method with parameter type and name, as provided', function () { +// const str = `getTime(String time) String$`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for static method with parameters, as provided', function () { +// const str = `getTime(String time, date Date)$ String`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe( +// 'getTime(String time, date Date) String' +// ); +// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); +// }); + +// it('should return correct css for abstract method with parameter type, as provided', function () { +// const str = `getTime(String) String*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String) String'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); + +// it('should return correct css for abstract method with parameter type and name, as provided', function () { +// const str = `getTime(String time) String*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); + +// it('should return correct css for abstract method with parameters, as provided', function () { +// const str = `getTime(String time, date Date) String*`; + +// const classMember = new ClassMember(str, 'method'); +// expect(classMember.getDisplayDetails().displayText).toBe( +// 'getTime(String time, date Date) String' +// ); +// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); +// }); +// }); + +// it('should handle declaration with single item in parameters with extra spaces', function () { +// const str = ' foo ( id) '; +// let actual = svgDraw.parseMember(str); + +// expect(actual.displayText).toBe('foo(id)'); +// expect(actual.cssStyle).toBe(''); +// }); + +// it('should handle method declaration with generic parameter', function () { +// const str = 'foo(List~int~)'; +// let actual = svgDraw.parseMember(str); + +// expect(actual.displayText).toBe('foo(List)'); +// 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); + +// expect(actual.displayText).toBe('+foo(List ids) : List'); +// expect(actual.cssStyle).toBe(abstractCssStyle); +// }); + +// it('should handle method declaration with nested generics', function () { +// const str = '+foo ( List~List~int~~ ids )* List~List~Item~~'; +// let actual = svgDraw.parseMember(str); + +// expect(actual.displayText).toBe('+foo(List> ids) : List>'); +// expect(actual.cssStyle).toBe(abstractCssStyle); +// }); + +// 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(staticCssStyle); +// }); + +// 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(staticCssStyle); +// }); + +// 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 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); + +// expect(actual.displayText).toBe('int[] ids'); +// expect(actual.cssStyle).toBe(''); +// }); + +// 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); + +// expect(actual.displayText).toBe('List ids'); +// expect(actual.cssStyle).toBe(''); +// }); + +// 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(''); +// }); + +// 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(staticCssStyle); +// }); + +// 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(staticCssStyle); +// }); + +// 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(staticCssStyle); +// }); + +// 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(staticCssStyle); +// }); + +// 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/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index becf6fe0d3..b9d7898da4 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -5,8 +5,8 @@ export interface ClassNode { type: string; label: string; cssClasses: string[]; - methods: string[]; - members: string[]; + methods: ClassMember[]; + members: ClassMember[]; annotations: string[]; domId: string; link?: string; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 37c8319d23..71ef127c8b 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -1,7 +1,6 @@ import { line, curveBasis } from 'd3'; import utils from '../../utils.js'; import { log } from '../../logger.js'; -import { parseGenericTypes } from '../common/common.js'; let edgeCount = 0; export const drawEdge = function (elem, path, relation, conf, diagObj) { @@ -372,81 +371,20 @@ export const drawNote = function (elem, note, conf, diagObj) { return noteInfo; }; -export const parseMember = function (text) { - let displayText = ''; - let cssStyle = ''; - let returnType = ''; - - let visibility = ''; - const firstChar = text.substring(0, 1); - const lastChar = text.substring(text.length - 1, text.length); - - if (firstChar.match(/[#+~-]/)) { - visibility = firstChar; - } - - const noClassifierRe = /[\s\w)~]/; - if (!lastChar.match(noClassifierRe)) { - cssStyle = parseClassifier(lastChar); - } - - const startIndex = visibility === '' ? 0 : 1; - const endIndex = cssStyle === '' ? text.length : text.length - 1; - text = text.substring(startIndex, endIndex); - - const methodStart = text.indexOf('('); - const methodEnd = text.indexOf(')'); - const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length; - - if (isMethod) { - const methodName = text.substring(0, methodStart).trim(); - const parameters = text.substring(methodStart + 1, methodEnd); - - displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; - - if (methodEnd < text.length) { - // 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; - } - } - } else { - // finally - if all else fails, just send the text back as written (other than parsing for generic types) - displayText = visibility + parseGenericTypes(text); - } - - return { - displayText, - cssStyle, - }; -}; - /** * Adds a for a member in a diagram * * @param {SVGElement} textEl The element to append to - * @param {string} txt The member + * @param {string} member The member * @param {boolean} isFirst * @param {{ padding: string; textHeight: string }} conf The configuration for the member */ -const addTspan = function (textEl, txt, isFirst, conf) { - let member = parseMember(txt); +const addTspan = function (textEl, member, isFirst, conf) { + const displayText = member.getDisplayDetails().displayText; + const cssStyle = member.getDisplayDetails().cssStyle; + const tSpan = textEl.append('tspan').attr('x', conf.padding).text(displayText); - const tSpan = textEl.append('tspan').attr('x', conf.padding).text(member.displayText); - - if (member.cssStyle !== '') { + if (cssStyle !== '') { tSpan.attr('style', member.cssStyle); } @@ -455,27 +393,9 @@ const addTspan = function (textEl, txt, isFirst, conf) { } }; -/** - * Gives the styles for a classifier - * - * @param {'+' | '-' | '#' | '~' | '*' | '$'} classifier The classifier string - * @returns {string} Styling for the classifier - */ -const parseClassifier = function (classifier) { - switch (classifier) { - case '*': - return 'font-style:italic;'; - case '$': - return 'text-decoration:underline;'; - default: - return ''; - } -}; - export default { getClassTitleString, drawClass, drawEdge, drawNote, - parseMember, }; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.spec.js b/packages/mermaid/src/diagrams/class/svgDraw.spec.js index 6f9b78d27d..f5e59af91b 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.spec.js @@ -1,341 +1,34 @@ import svgDraw from './svgDraw.js'; describe('given a string representing a class, ', 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'); - }); - it('should handle class names with nested generics', function () { - const classDef = { - id: 'Car', - type: 'T~TT~', - label: 'Car', - }; - - let actual = svgDraw.getClassTitleString(classDef); - expect(actual).toBe('Car>'); - }); - - describe('when parsing member method', function () { - it('should handle simple declaration', function () { - const str = 'foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle declaration with parameters', function () { - const str = 'foo(int id)'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(int id)'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle declaration with multiple parameters', function () { - const str = 'foo(int id, object thing)'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(int id, object thing)'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle declaration with single item in parameters', function () { - const str = 'foo(id)'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(id)'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle declaration with single item in parameters with extra spaces', function () { - const str = ' foo ( id) '; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(id)'); - expect(actual.cssStyle).toBe(''); - }); - - it('should handle method declaration with generic parameter', function () { - const str = 'foo(List~int~)'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('foo(List)'); - 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); - - expect(actual.displayText).toBe('+foo(List ids) : List'); - expect(actual.cssStyle).toBe('font-style:italic;'); - }); - - it('should handle method declaration with nested generics', function () { - const str = '+foo ( List~List~int~~ ids )* List~List~Item~~'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('+foo(List> ids) : List>'); - expect(actual.cssStyle).toBe('font-style:italic;'); - }); - - it('should correctly handle public visibility', function () { - const str = '+foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('+foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should correctly handle private visibility', function () { - const str = '-foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('-foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should correctly handle protected visibility', function () { - const str = '#foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('#foo()'); - expect(actual.cssStyle).toBe(''); - }); - - it('should correctly handle package/internal visibility', function () { - const str = '~foo()'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('~foo()'); - expect(actual.cssStyle).toBe(''); - }); - - 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('when class name includes generic, ', function () { + it('should return correct text for generic', function () { + const classDef = { + id: 'Car', + type: 'T', + label: 'Car', + }; + + let actual = svgDraw.getClassTitleString(classDef); + expect(actual).toBe('Car'); + }); + it('should return correct text for nested generics', function () { + const classDef = { + id: 'Car', + type: 'T~TT~', + label: 'Car', + }; + + let actual = svgDraw.getClassTitleString(classDef); + expect(actual).toBe('Car>'); }); }); + describe('when class has no members, ', function () { + it('should have no members', function () { + const str = 'class Class10'; + let actual = svgDraw.drawClass(str); - describe('when parsing member field', function () { - it('should handle simple field', function () { - const str = 'id'; - let actual = svgDraw.parseMember(str); - - expect(actual.displayText).toBe('id'); - expect(actual.cssStyle).toBe(''); + expect(actual.displayText).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); - - expect(actual.displayText).toBe('int[] ids'); - expect(actual.cssStyle).toBe(''); - }); - - 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); - - expect(actual.displayText).toBe('List ids'); - expect(actual.cssStyle).toBe(''); - }); - - 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(''); - }); - - 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(''); - }); - }); -}); - -describe('given a string representing class with no members, ', function () { - it('should have no members', function () { - const str = 'class Class10'; - let actual = svgDraw.drawClass(str); - - expect(actual.displayText).toBe(''); }); }); From 164605b44236f3ddd2cc69a8a9404c7e8adb7fc2 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 12 Jun 2023 11:31:29 -0700 Subject: [PATCH 03/56] update classes to handle , in generic --- demos/classchart.html | 4 + packages/mermaid/src/dagre-wrapper/nodes.js | 9 +- .../src/diagrams/class/classDiagram.spec.ts | 68 +- .../src/diagrams/class/classParser.spec.ts | 7 +- .../mermaid/src/diagrams/class/classParser.ts | 5 +- .../src/diagrams/class/classTypes.spec.ts | 990 +++++++++--------- .../mermaid/src/diagrams/class/classTypes.ts | 7 +- .../mermaid/src/diagrams/class/svgDraw.js | 3 +- .../src/diagrams/class/svgDraw.spec.js | 11 +- .../mermaid/src/diagrams/common/common.ts | 2 +- 10 files changed, 569 insertions(+), 537 deletions(-) diff --git a/demos/classchart.html b/demos/classchart.html index b20dda2a33..39e0631ecd 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -80,6 +80,7 @@

Class diagram demos

Class01 : #size() Class01 : -int chimp Class01 : +int gorilla + Class01 : +abstractAttribute string* class Class10~T~ { <<service>> int id @@ -122,6 +123,8 @@

Class diagram demos

classDiagram direction LR Animal ()-- Dog + Animal ()-- Cat + note for Cat "should have no members area" Dog : bark() Dog : species() @@ -151,6 +154,7 @@

Class diagram demos

~InternalProperty : string ~AnotherInternalProperty : List~List~string~~ } + class People~List~Person~~
diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index b842fa9a52..72b8964dcb 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -5,7 +5,6 @@ import { getConfig } from '../config.js'; import intersect from './intersect/index.js'; import createLabel from './createLabel.js'; import note from './shapes/note.js'; -import { parseMember } from '../diagrams/class/svgDraw.js'; import { evaluate } from '../diagrams/common/common.js'; const question = async (parent, node) => { @@ -806,8 +805,8 @@ const class_box = (parent, node) => { maxWidth = classTitleBBox.width; } const classAttributes = []; - node.classData.members.forEach((str) => { - const parsedInfo = parseMember(str); + node.classData.members.forEach((member) => { + const parsedInfo = member.getDisplayDetails(); let parsedText = parsedInfo.displayText; if (getConfig().flowchart.htmlLabels) { parsedText = parsedText.replace(//g, '>'); @@ -840,8 +839,8 @@ const class_box = (parent, node) => { maxHeight += lineHeight; const classMethods = []; - node.classData.methods.forEach((str) => { - const parsedInfo = parseMember(str); + node.classData.methods.forEach((member) => { + const parsedInfo = member.getDisplayDetails(); let displayText = parsedInfo.displayText; if (getConfig().flowchart.htmlLabels) { displayText = displayText.replace(//g, '>'); diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 9da413b6a6..fded1eb7d3 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -4,6 +4,9 @@ import classParser from './classParser.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; +const staticCssStyle = 'text-decoration:underline;'; +const abstractCssStyle = 'font-style:italic;'; + describe('given a basic class diagram, ', function () { describe('when parsing class definition', function () { beforeEach(function () { @@ -127,7 +130,7 @@ describe('given a basic class diagram, ', function () { const c1 = classParser.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].getDisplayDetails().displayText).toBe('member1'); }); it('should parse a class with a text label, member and annotation', () => { @@ -142,7 +145,7 @@ describe('given a basic class diagram, ', function () { const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); - expect(c1.members[0]).toBe('int member1'); + expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); }); @@ -168,7 +171,7 @@ describe('given a basic class diagram, ', function () { const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - expect(c1.members[0]).toBe('int member1'); + expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); expect(c1.cssClasses[0]).toBe('styleClass'); }); @@ -315,8 +318,8 @@ class C13["With Città foreign language"] 'classDiagram\n' + 'class Class1 {\n' + 'int testMember\n' + - 'string fooMember\n' + 'test()\n' + + 'string fooMember\n' + 'foo()\n' + '}'; parser.parse(str); @@ -324,10 +327,10 @@ class C13["With Città foreign language"] const actual = parser.yy.getClass('Class1'); 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()'); + expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember'); + expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember'); + expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()'); + expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()'); }); it('should parse a class with a text label and members', () => { @@ -337,7 +340,7 @@ class C13["With Città foreign language"] const c1 = classParser.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].getDisplayDetails().displayText).toBe('+member1'); }); it('should parse a class with a text label, members and annotation', () => { @@ -352,7 +355,7 @@ class C13["With Città foreign language"] const c1 = classParser.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].getDisplayDetails().displayText).toBe('+member1'); expect(c1.annotations.length).toBe(1); expect(c1.annotations[0]).toBe('interface'); }); @@ -655,10 +658,10 @@ describe('given a class diagram with members and methods ', function () { 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'); + expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember'); + expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember'); + expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember'); + expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage'); }); it('should handle generic types', function () { @@ -711,7 +714,9 @@ describe('given a class diagram with members and methods ', function () { expect(actual.annotations.length).toBe(0); expect(actual.members.length).toBe(0); expect(actual.methods.length).toBe(1); - expect(actual.methods[0]).toBe('someMethod()*'); + const method = actual.methods[0]; + expect(method.getDisplayDetails().displayText).toBe('someMethod()'); + expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); it('should handle static methods', function () { @@ -722,7 +727,9 @@ describe('given a class diagram with members and methods ', function () { expect(actual.annotations.length).toBe(0); expect(actual.members.length).toBe(0); expect(actual.methods.length).toBe(1); - expect(actual.methods[0]).toBe('someMethod()$'); + const method = actual.methods[0]; + expect(method.getDisplayDetails().displayText).toBe('someMethod()'); + expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle); }); it('should handle generic types in arguments', function () { @@ -1167,10 +1174,10 @@ describe('given a class diagram with relationships, ', function () { 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()'); + expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test'); + expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo'); + expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()'); + expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()'); }); it('should handle abstract methods', function () { @@ -1181,7 +1188,9 @@ describe('given a class diagram with relationships, ', function () { expect(testClass.annotations.length).toBe(0); expect(testClass.members.length).toBe(0); expect(testClass.methods.length).toBe(1); - expect(testClass.methods[0]).toBe('someMethod()*'); + const method = testClass.methods[0]; + expect(method.getDisplayDetails().displayText).toBe('someMethod()'); + expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); it('should handle static methods', function () { @@ -1192,7 +1201,9 @@ describe('given a class diagram with relationships, ', function () { expect(testClass.annotations.length).toBe(0); expect(testClass.members.length).toBe(0); expect(testClass.methods.length).toBe(1); - expect(testClass.methods[0]).toBe('someMethod()$'); + const method = testClass.methods[0]; + expect(method.getDisplayDetails().displayText).toBe('someMethod()'); + expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle); }); it('should associate link and css appropriately', function () { @@ -1418,8 +1429,8 @@ class Class2 const c1 = classParser.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); - expect(c1.members[0]).toBe('+member1'); - + const member = c1.members[0]; + expect(member.getDisplayDetails().displayText).toBe('+member1'); const c2 = classParser.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1435,9 +1446,10 @@ class Class2 const c1 = classParser.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'); + const member = c1.members[0]; + expect(member.getDisplayDetails().displayText).toBe('+member1'); const c2 = classParser.getClass('C2'); expect(c2.label).toBe('C2'); @@ -1454,8 +1466,9 @@ C1 --> C2 const c1 = classParser.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'); + const member = c1.members[0]; + expect(member.getDisplayDetails().displayText).toBe('+member1'); }); it('should parse a class with text label and css class', () => { @@ -1470,8 +1483,9 @@ cssClass "C1" styleClass const c1 = classParser.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'); + const member = c1.members[0]; + expect(member.getDisplayDetails().displayText).toBe('+member1'); }); it('should parse two classes with text labels and css classes', () => { diff --git a/packages/mermaid/src/diagrams/class/classParser.spec.ts b/packages/mermaid/src/diagrams/class/classParser.spec.ts index ed402c28ff..df0e44f9ec 100644 --- a/packages/mermaid/src/diagrams/class/classParser.spec.ts +++ b/packages/mermaid/src/diagrams/class/classParser.spec.ts @@ -39,7 +39,12 @@ describe('when parsing class diagram', function () { "id": "Student", "label": "Student", "members": [ - "-idCard : IdCard", + ClassMember { + "classifier": "", + "id": "idCard : IdCard", + "memberType": "attribute", + "visibility": "-", + }, ], "methods": [], "type": "", diff --git a/packages/mermaid/src/diagrams/class/classParser.ts b/packages/mermaid/src/diagrams/class/classParser.ts index 5c2751dc99..2e24fc1512 100644 --- a/packages/mermaid/src/diagrams/class/classParser.ts +++ b/packages/mermaid/src/diagrams/class/classParser.ts @@ -21,6 +21,7 @@ import { ClassMap, NamespaceMap, NamespaceNode, + ClassMember, } from './classTypes.js'; const MERMAID_DOM_ID_PREFIX = 'classId-'; @@ -186,9 +187,9 @@ export const addMember = function (className: string, member: string) { theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); } else if (memberString.indexOf(')') > 0) { //its a method - theClass.methods.push(sanitizeText(memberString)); + theClass.methods.push(new ClassMember(memberString, 'method')); } else if (memberString) { - theClass.members.push(sanitizeText(memberString)); + theClass.members.push(new ClassMember(memberString, 'attribute')); } } }; diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index cd13cb0eb2..07cb0d3f51 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -5,597 +5,607 @@ const spyOn = vi.spyOn; const staticCssStyle = 'text-decoration:underline;'; const abstractCssStyle = 'font-style:italic;'; -describe('given text representing a member, ', function () { - describe('when parseMember is called as method', function () { - describe('when method has no parameters', function () { - it('should parse correctly', function () { - const str = `getTime()`; +describe('given text representing a method, ', function () { + describe('when method has no parameters', function () { + it('should parse correctly', function () { + const str = `getTime()`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle public visibility', function () { - const str = `+getTime()`; + it('should handle public visibility', function () { + const str = `+getTime()`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); + }); - it('should handle private visibility', function () { - const str = `-getTime()`; + it('should handle private visibility', function () { + const str = `-getTime()`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); + }); - it('should handle protected visibility', function () { - const str = `#getTime()`; + it('should handle protected visibility', function () { + const str = `#getTime()`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); + }); - it('should handle internal visibility', function () { - const str = `~getTime()`; + it('should handle internal visibility', function () { + const str = `~getTime()`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); + }); - it('should return correct css for static classifier', function () { - const str = `getTime()$`; + it('should return correct css for static classifier', function () { + const str = `getTime()$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract classifier', function () { - const str = `getTime()*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime()*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + }); - describe('when method has single parameter value', function () { - it('should parse correctly', function () { - const str = `getTime(int)`; + describe('when method has single parameter value', function () { + it('should parse correctly', function () { + const str = `getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle public visibility', function () { - const str = `+getTime(int)`; + it('should handle public visibility', function () { + const str = `+getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int)'); + }); - it('should handle private visibility', function () { - const str = `-getTime(int)`; + it('should handle private visibility', function () { + const str = `-getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int)'); + }); - it('should handle protected visibility', function () { - const str = `#getTime(int)`; + it('should handle protected visibility', function () { + const str = `#getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int)'); + }); - it('should handle internal visibility', function () { - const str = `~getTime(int)`; + it('should handle internal visibility', function () { + const str = `~getTime(int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int)'); + }); - it('should return correct css for static classifier', function () { - const str = `getTime(int)$`; + it('should return correct css for static classifier', function () { + const str = `getTime(int)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract classifier', function () { - const str = `getTime(int)*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(int)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + }); - describe('when method has single parameter type and name (type first)', function () { - it('should parse correctly', function () { - const str = `getTime(int count)`; + describe('when method has single parameter type and name (type first)', function () { + it('should parse correctly', function () { + const str = `getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle public visibility', function () { - const str = `+getTime(int count)`; + it('should handle public visibility', function () { + const str = `+getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int count)'); + }); - it('should handle private visibility', function () { - const str = `-getTime(int count)`; + it('should handle private visibility', function () { + const str = `-getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int count)'); + }); - it('should handle protected visibility', function () { - const str = `#getTime(int count)`; + it('should handle protected visibility', function () { + const str = `#getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int count)'); + }); - it('should handle internal visibility', function () { - const str = `~getTime(int count)`; + it('should handle internal visibility', function () { + const str = `~getTime(int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int count)'); + }); - it('should return correct css for static classifier', function () { - const str = `getTime(int count)$`; + it('should return correct css for static classifier', function () { + const str = `getTime(int count)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract classifier', function () { - const str = `getTime(int count)*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(int count)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + }); - describe('when method has single parameter type and name (name first)', function () { - it('should parse correctly', function () { - const str = `getTime(count int)`; + describe('when method has single parameter type and name (name first)', function () { + it('should parse correctly', function () { + const str = `getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle public visibility', function () { - const str = `+getTime(count int)`; + it('should handle public visibility', function () { + const str = `+getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime(count int)'); + }); - it('should handle private visibility', function () { - const str = `-getTime(count int)`; + it('should handle private visibility', function () { + const str = `-getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime(count int)'); + }); - it('should handle protected visibility', function () { - const str = `#getTime(count int)`; + it('should handle protected visibility', function () { + const str = `#getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime(count int)'); + }); - it('should handle internal visibility', function () { - const str = `~getTime(count int)`; + it('should handle internal visibility', function () { + const str = `~getTime(count int)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime(count int)'); + }); - it('should return correct css for static classifier', function () { - const str = `getTime(count int)$`; + it('should return correct css for static classifier', function () { + const str = `getTime(count int)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract classifier', function () { - const str = `getTime(count int)*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(count int)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); + }); - describe('when method has multiple parameters', function () { - it('should parse correctly', function () { - const str = `getTime(string text, int count)`; + describe('when method has multiple parameters', function () { + it('should parse correctly', function () { + const str = `getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe(str); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle public visibility', function () { - const str = `+getTime(string text, int count)`; + it('should handle public visibility', function () { + const str = `+getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle private visibility', function () { - const str = `-getTime(string text, int count)`; + it('should handle private visibility', function () { + const str = `-getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle protected visibility', function () { - const str = `#getTime(string text, int count)`; + it('should handle protected visibility', function () { + const str = `#getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should handle internal visibility', function () { - const str = `~getTime(string text, int count)`; + it('should handle internal visibility', function () { + const str = `~getTime(string text, int count)`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTime()'); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(str); + }); - it('should return correct css for static classifier', function () { - const str = `getTime(string text, int count)$`; + it('should return correct css for static classifier', function () { + const str = `getTime(string text, int count)$`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); - it('should return correct css for abstract classifier', function () { - const str = `getTime(string text, int count)*`; + it('should return correct css for abstract classifier', function () { + const str = `getTime(string text, int count)*`; - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); - }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); }); -}); -// it('should return correct css for static method with parameter type, as provided', function () { -// const str = `getTime(String)$`; + describe('when method has return type', function () { + it('should parse correctly', function () { + const str = `getTime() DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); + }); + + it('should handle public visibility', function () { + const str = `+getTime() DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTime() : DateTime'); + }); + + it('should handle private visibility', function () { + const str = `-getTime() DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTime() : DateTime'); + }); + + it('should handle protected visibility', function () { + const str = `#getTime() DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTime() : DateTime'); + }); + + it('should handle internal visibility', function () { + const str = `~getTime() DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTime() : DateTime'); + }); + + it('should return correct css for static classifier', function () { + const str = `getTime() DateTime$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); + it('should return correct css for abstract classifier', function () { + const str = `getTime() DateTime*`; -// it('should return correct css for static method with parameter type and name, as provided', function () { -// const str = `getTime(String time)$`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); + describe('when method parameter is generic', function () { + it('should parse correctly', function () { + const str = `getTimes(List~T~)`; -// it('should return correct css for static method with parameters, as provided', function () { -// const str = `getTime(String time, date Date)$`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); + it('should handle public visibility', function () { + const str = `+getTimes(List~T~)`; -// it('should return correct css for abstract method with parameter type, as provided', function () { -// const str = `getTime(String)*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); + it('should handle private visibility', function () { + const str = `-getTimes(List~T~)`; -// it('should return correct css for abstract method with parameter type and name, as provided', function () { -// const str = `getTime(String time)*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); + it('should handle protected visibility', function () { + const str = `#getTimes(List~T~)`; -// it('should return correct css for abstract method with parameters, as provided', function () { -// const str = `getTime(String time, date Date)*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time, date Date)'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); -// }); + it('should handle internal visibility', function () { + const str = `~getTimes(List~T~)`; -// describe('when text is a method with return type', function () { -// it('should parse simple method with no parameter', function () { -// const str = `getTime() String`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe(str); -// }); + it('should return correct css for static classifier', function () { + const str = `getTimes(List~T~)$`; -// it('should parse method with parameter type, as provided', function () { -// const str = `getTime(String) String`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe(str); -// }); + it('should return correct css for abstract classifier', function () { + const str = `getTimes(List~T~)*`; -// it('should parse method with parameter type and name, as provided', function () { -// const str = `getTime(String time) String`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List>List>)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe(str); -// }); + describe('when method parameter is a nested generic', function () { + it('should parse correctly', function () { + const str = `getTimetableList(List~List~T~~)`; -// it('should parse method with parameters, as provided', function () { -// const str = `getTime(String time, date Date) String`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe(str); -// }); + it('should handle public visibility', function () { + const str = `+getTimetableList(List~List~T~~)`; -// it('should return correct css for static method with no parameter', function () { -// const str = `getTime() String$`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTimetableList(List>)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime() String'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); + it('should handle private visibility', function () { + const str = `-getTimetableList(List~List~T~~)`; -// it('should return correct css for static method with parameter type and name, as provided', function () { -// const str = `getTime(String time) String$`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTimetableList(List>)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); + it('should handle protected visibility', function () { + const str = `#getTimetableList(List~List~T~~)`; -// it('should return correct css for static method with parameters, as provided', function () { -// const str = `getTime(String time, date Date)$ String`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTimetableList(List>)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe( -// 'getTime(String time, date Date) String' -// ); -// expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); -// }); - -// it('should return correct css for abstract method with parameter type, as provided', function () { -// const str = `getTime(String) String*`; + it('should handle internal visibility', function () { + const str = `~getTimetableList(List~List~T~~)`; -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String) String'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); - -// it('should return correct css for abstract method with parameter type and name, as provided', function () { -// const str = `getTime(String time) String*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTimetableList(List>)'); + }); -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe('getTime(String time) String'); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); - -// it('should return correct css for abstract method with parameters, as provided', function () { -// const str = `getTime(String time, date Date) String*`; + it('should return correct css for static classifier', function () { + const str = `getTimetableList(List~List~T~~)$`; -// const classMember = new ClassMember(str, 'method'); -// expect(classMember.getDisplayDetails().displayText).toBe( -// 'getTime(String time, date Date) String' -// ); -// expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); -// }); -// }); - -// it('should handle declaration with single item in parameters with extra spaces', function () { -// const str = ' foo ( id) '; -// let actual = svgDraw.parseMember(str); - -// expect(actual.displayText).toBe('foo(id)'); -// expect(actual.cssStyle).toBe(''); -// }); - -// it('should handle method declaration with generic parameter', function () { -// const str = 'foo(List~int~)'; -// let actual = svgDraw.parseMember(str); - -// expect(actual.displayText).toBe('foo(List)'); -// 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); - -// expect(actual.displayText).toBe('+foo(List ids) : List'); -// expect(actual.cssStyle).toBe(abstractCssStyle); -// }); - -// it('should handle method declaration with nested generics', function () { -// const str = '+foo ( List~List~int~~ ids )* List~List~Item~~'; -// let actual = svgDraw.parseMember(str); - -// expect(actual.displayText).toBe('+foo(List> ids) : List>'); -// expect(actual.cssStyle).toBe(abstractCssStyle); -// }); - -// 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(staticCssStyle); -// }); - -// 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(staticCssStyle); -// }); - -// 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 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); - -// expect(actual.displayText).toBe('int[] ids'); -// expect(actual.cssStyle).toBe(''); -// }); - -// 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); - -// expect(actual.displayText).toBe('List ids'); -// expect(actual.cssStyle).toBe(''); -// }); - -// 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(''); -// }); - -// 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(staticCssStyle); -// }); - -// 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(staticCssStyle); -// }); - -// 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(staticCssStyle); -// }); - -// 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(staticCssStyle); -// }); - -// 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(''); -// }); -// }); -// }); + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTimetableList(List~List~T~~)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List>)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); + + describe('when method parameter is a composite generic', function () { + const methodNameAndParameters = 'getTimes(List~K, V~)'; + const expectedMethodNameAndParameters = 'getTimes(List)'; + it('should parse correctly', function () { + const str = methodNameAndParameters; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); + }); + + it('should handle public visibility', function () { + const str = '+' + methodNameAndParameters; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '+' + expectedMethodNameAndParameters + ); + }); + + it('should handle private visibility', function () { + const str = '-' + methodNameAndParameters; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '-' + expectedMethodNameAndParameters + ); + }); + + it('should handle protected visibility', function () { + const str = '#' + methodNameAndParameters; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '#' + expectedMethodNameAndParameters + ); + }); + + it('should handle internal visibility', function () { + const str = '~' + methodNameAndParameters; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '~' + expectedMethodNameAndParameters + ); + }); + + it('should return correct css for static classifier', function () { + const str = methodNameAndParameters + '$'; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = methodNameAndParameters + '*'; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); + + describe('when method return type is generic', function () { + it('should parse correctly', function () { + const str = `getTimes() List~T~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); + }); + + it('should handle public visibility', function () { + const str = `+getTimes() List~T~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTimes() : List'); + }); + + it('should handle private visibility', function () { + const str = `-getTimes() List~T~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTimes() : List'); + }); + + it('should handle protected visibility', function () { + const str = `#getTimes() List~T~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTimes() : List'); + }); + + it('should handle internal visibility', function () { + const str = `~getTimes() List~T~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTimes() : List'); + }); + + it('should return correct css for static classifier', function () { + const str = `getTimes() List~T~$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTimes() List~T~*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); + + describe('when method return type is a nested generic', function () { + it('should parse correctly', function () { + const str = `getTimetableList() List~List~T~~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTimetableList() : List>' + ); + }); + + it('should handle public visibility', function () { + const str = `+getTimetableList() List~List~T~~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '+getTimetableList() : List>' + ); + }); + + it('should handle private visibility', function () { + const str = `-getTimetableList() List~List~T~~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '-getTimetableList() : List>' + ); + }); + + it('should handle protected visibility', function () { + const str = `#getTimetableList() List~List~T~~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '#getTimetableList() : List>' + ); + }); + + it('should handle internal visibility', function () { + const str = `~getTimetableList() List~List~T~~`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + '~getTimetableList() : List>' + ); + }); + + it('should return correct css for static classifier', function () { + const str = `getTimetableList() List~List~T~~$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTimetableList() : List>' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTimetableList() List~List~T~~*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTimetableList() : List>' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index b9d7898da4..67dc0acb66 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -26,13 +26,18 @@ export class ClassMember { constructor(input: string, memberType: string) { this.memberType = memberType; + this.visibility = ''; + this.classifier = ''; this.parseMember(input); } getDisplayDetails() { let displayText = this.visibility + parseGenericTypes(this.id); if (this.memberType === 'method') { - displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')' + ' ' + this.returnType; + displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')'; + if (this.returnType) { + displayText += ' : ' + parseGenericTypes(this.returnType); + } } displayText = displayText.trim(); diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 71ef127c8b..ccc79aebbe 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -1,6 +1,7 @@ import { line, curveBasis } from 'd3'; import utils from '../../utils.js'; import { log } from '../../logger.js'; +import { parseGenericTypes } from '../common/common.js'; let edgeCount = 0; export const drawEdge = function (elem, path, relation, conf, diagObj) { @@ -302,7 +303,7 @@ export const getClassTitleString = function (classDef) { let classTitleString = classDef.id; if (classDef.type) { - classTitleString += '<' + classDef.type + '>'; + classTitleString += '<' + parseGenericTypes(classDef.type) + '>'; } return classTitleString; diff --git a/packages/mermaid/src/diagrams/class/svgDraw.spec.js b/packages/mermaid/src/diagrams/class/svgDraw.spec.js index f5e59af91b..f068f8d626 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.spec.js @@ -1,4 +1,5 @@ import svgDraw from './svgDraw.js'; +import { JSDOM } from 'jsdom'; describe('given a string representing a class, ', function () { describe('when class name includes generic, ', function () { @@ -15,7 +16,7 @@ describe('given a string representing a class, ', function () { it('should return correct text for nested generics', function () { const classDef = { id: 'Car', - type: 'T~TT~', + type: 'T~T~', label: 'Car', }; @@ -23,12 +24,4 @@ describe('given a string representing a class, ', function () { expect(actual).toBe('Car>'); }); }); - describe('when class has no members, ', function () { - it('should have no members', function () { - const str = 'class Class10'; - let actual = svgDraw.drawClass(str); - - expect(actual.displayText).toBe(''); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 275e16a8e8..cf6f8cb326 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -189,7 +189,7 @@ export const parseGenericTypes = function (text: string): string { do { cleanedText = newCleanedText; - newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>'); + newCleanedText = cleanedText.replace(/~([^:;]+)~/, '<$1>'); } while (newCleanedText != cleanedText); return parseGenericTypes(newCleanedText); From a9ee184551813a595929e6201a222947f78f6ca4 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 12 Jun 2023 14:23:36 -0700 Subject: [PATCH 04/56] spec changes --- .../src/diagrams/class/classDiagram.spec.ts | 115 +++++++++++------- .../src/diagrams/class/classParser.spec.ts | 68 ----------- 2 files changed, 68 insertions(+), 115 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index fded1eb7d3..21863d67a9 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -267,6 +267,74 @@ class C13["With Città foreign language"] const str = 'classDiagram\n' + 'note "test"\n'; parser.parse(str); }); + + it('should parse diagram with direction', () => { + parser.parse(`classDiagram + direction TB + class Student { + -idCard : IdCard + } + class IdCard{ + -id : int + -name : string + } + class Bike{ + -id : int + -name : string + } + Student "1" --o "1" IdCard : carries + Student "1" --o "1" Bike : rides`); + + expect(Object.keys(classParser.getClasses()).length).toBe(3); + expect(classParser.getClasses().Student).toMatchInlineSnapshot(` + { + "annotations": [], + "cssClasses": [], + "domId": "classId-Student-0", + "id": "Student", + "label": "Student", + "members": [ + ClassMember { + "classifier": "", + "id": "idCard : IdCard", + "memberType": "attribute", + "visibility": "-", + }, + ], + "methods": [], + "type": "", + } + `); + expect(classParser.getRelations().length).toBe(2); + expect(classParser.getRelations()).toMatchInlineSnapshot(` + [ + { + "id1": "Student", + "id2": "IdCard", + "relation": { + "lineType": 0, + "type1": "none", + "type2": 0, + }, + "relationTitle1": "1", + "relationTitle2": "1", + "title": "carries", + }, + { + "id1": "Student", + "id2": "Bike", + "relation": { + "lineType": 0, + "type1": "none", + "type2": 0, + }, + "relationTitle1": "1", + "relationTitle2": "1", + "title": "rides", + }, + ] + `); + }); }); describe('when parsing class defined in brackets', function () { @@ -855,53 +923,6 @@ foo() parser.parse(str); }); }); - - describe('when parsing invalid generic classes', function () { - beforeEach(function () { - classParser.clear(); - parser.yy = classParser; - }); - - 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 () { diff --git a/packages/mermaid/src/diagrams/class/classParser.spec.ts b/packages/mermaid/src/diagrams/class/classParser.spec.ts index df0e44f9ec..e797a9c93b 100644 --- a/packages/mermaid/src/diagrams/class/classParser.spec.ts +++ b/packages/mermaid/src/diagrams/class/classParser.spec.ts @@ -12,72 +12,4 @@ describe('when parsing class diagram', function () { classDiagram.parser.yy = classParser; classDiagram.parser.yy.clear(); }); - - it('should parse diagram with direction', () => { - classDiagram.parser.parse(`classDiagram - direction TB - class Student { - -idCard : IdCard - } - class IdCard{ - -id : int - -name : string - } - class Bike{ - -id : int - -name : string - } - Student "1" --o "1" IdCard : carries - Student "1" --o "1" Bike : rides`); - - expect(Object.keys(classParser.getClasses()).length).toBe(3); - expect(classParser.getClasses().Student).toMatchInlineSnapshot(` - { - "annotations": [], - "cssClasses": [], - "domId": "classId-Student-0", - "id": "Student", - "label": "Student", - "members": [ - ClassMember { - "classifier": "", - "id": "idCard : IdCard", - "memberType": "attribute", - "visibility": "-", - }, - ], - "methods": [], - "type": "", - } - `); - expect(classParser.getRelations().length).toBe(2); - expect(classParser.getRelations()).toMatchInlineSnapshot(` - [ - { - "id1": "Student", - "id2": "IdCard", - "relation": { - "lineType": 0, - "type1": "none", - "type2": 0, - }, - "relationTitle1": "1", - "relationTitle2": "1", - "title": "carries", - }, - { - "id1": "Student", - "id2": "Bike", - "relation": { - "lineType": 0, - "type1": "none", - "type2": 0, - }, - "relationTitle1": "1", - "relationTitle2": "1", - "title": "rides", - }, - ] - `); - }); }); From b32da2b1b197d74c5fb200e32b52ea29420ecef5 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 25 Jun 2023 15:07:47 -0700 Subject: [PATCH 05/56] Fix tests --- .../src/diagrams/class/classDiagram.spec.ts | 2 +- .../src/diagrams/class/classParser.spec.ts | 15 --------------- .../mermaid/src/diagrams/class/classTypes.spec.ts | 4 ++-- 3 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 packages/mermaid/src/diagrams/class/classParser.spec.ts diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 21863d67a9..54821361a8 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -290,7 +290,7 @@ class C13["With Città foreign language"] { "annotations": [], "cssClasses": [], - "domId": "classId-Student-0", + "domId": "classId-Student-34", "id": "Student", "label": "Student", "members": [ diff --git a/packages/mermaid/src/diagrams/class/classParser.spec.ts b/packages/mermaid/src/diagrams/class/classParser.spec.ts deleted file mode 100644 index e797a9c93b..0000000000 --- a/packages/mermaid/src/diagrams/class/classParser.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { setConfig } from '../../config.js'; -import classParser from './classParser.js'; -// @ts-ignore - no types in jison -import classDiagram from './parser/classDiagram.jison'; - -setConfig({ - securityLevel: 'strict', -}); - -describe('when parsing class diagram', function () { - beforeEach(function () { - classDiagram.parser.yy = classParser; - classDiagram.parser.yy.clear(); - }); -}); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index 07cb0d3f51..7ae6ae8311 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -372,7 +372,7 @@ describe('given text representing a method, ', function () { const str = `getTimes(List~T~)*`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List>List>)'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List)'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); }); @@ -537,7 +537,7 @@ describe('given text representing a method, ', function () { const str = `getTimes() List~T~*`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List)'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List'); expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); }); From 7b2ef1110a273031673ddb1c6f28d4289a44f3bf Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 28 Jun 2023 12:37:32 -0700 Subject: [PATCH 06/56] update tests and db name --- packages/mermaid-zenuml/README.md | 2 +- .../class/{classParser.ts => classDb.ts} | 0 .../class/classDiagram-styles.spec.js | 4 +- .../src/diagrams/class/classDiagram-v2.ts | 6 +- .../src/diagrams/class/classDiagram.spec.ts | 288 +++++++++--------- .../src/diagrams/class/classDiagram.ts | 6 +- .../src/diagrams/common/common.spec.js | 1 - 7 files changed, 153 insertions(+), 154 deletions(-) rename packages/mermaid/src/diagrams/class/{classParser.ts => classDb.ts} (100%) diff --git a/packages/mermaid-zenuml/README.md b/packages/mermaid-zenuml/README.md index e807400636..4300aecbe0 120000 --- a/packages/mermaid-zenuml/README.md +++ b/packages/mermaid-zenuml/README.md @@ -1 +1 @@ -../mermaid/src/docs/syntax/zenuml.md \ No newline at end of file +../mermaid/src/docs/syntax/zenuml.md diff --git a/packages/mermaid/src/diagrams/class/classParser.ts b/packages/mermaid/src/diagrams/class/classDb.ts similarity index 100% rename from packages/mermaid/src/diagrams/class/classParser.ts rename to packages/mermaid/src/diagrams/class/classDb.ts diff --git a/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js b/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js index fe01854b0f..a693fbbeab 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js +++ b/packages/mermaid/src/diagrams/class/classDiagram-styles.spec.js @@ -1,10 +1,10 @@ import { parser } from './parser/classDiagram.jison'; -import classParser from './classParser.js'; +import classDb from './classDb.js'; describe('class diagram, ', function () { describe('when parsing data from a classDiagram it', function () { beforeEach(function () { - parser.yy = classParser; + parser.yy = classDb; parser.yy.clear(); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts index c40d36c535..2768e22564 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts @@ -1,13 +1,13 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: TODO Fix ts errors import parser from './parser/classDiagram.jison'; -import db from './classParser.js'; +import classDb from './classDb.js'; import styles from './styles.js'; import renderer from './classRenderer-v2.js'; export const diagram: DiagramDefinition = { parser, - db, + db: classDb, renderer, styles, init: (cnf) => { @@ -15,6 +15,6 @@ export const diagram: DiagramDefinition = { cnf.class = {}; } cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - db.clear(); + classDb.clear(); }, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 54821361a8..f052f7fd58 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1,6 +1,6 @@ // @ts-expect-error Jison doesn't export types import { parser } from './parser/classDiagram.jison'; -import classParser from './classParser.js'; +import classDb from './classDb.js'; import { vi, describe, it, expect } from 'vitest'; const spyOn = vi.spyOn; @@ -10,8 +10,8 @@ const abstractCssStyle = 'font-style:italic;'; describe('given a basic class diagram, ', function () { describe('when parsing class definition', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle accTitle and accDescr', function () { const str = `classDiagram @@ -57,7 +57,7 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class Ca-r'; parser.parse(str); - const actual = classParser.getClass('Ca-r'); + const actual = classDb.getClass('Ca-r'); expect(actual.label).toBe('Ca-r'); }); @@ -105,7 +105,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); }); @@ -117,9 +117,9 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 with chars @?'); }); @@ -127,7 +127,7 @@ describe('given a basic class diagram, ', function () { const str = 'classDiagram\n' + 'class C1["Class 1 with text label"]\n' + 'C1: member1'; parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0].getDisplayDetails().displayText).toBe('member1'); @@ -142,7 +142,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); @@ -155,7 +155,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass'); }); @@ -169,7 +169,7 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1'); expect(c1.cssClasses[0]).toBe('styleClass'); @@ -185,11 +185,11 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Long long long long long long long long long long label'); expect(c2.cssClasses[0]).toBe('styleClass'); }); @@ -202,11 +202,11 @@ describe('given a basic class diagram, ', function () { parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.cssClasses[0]).toBe('styleClass1'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 !@#$%^&*() label'); expect(c2.cssClasses[0]).toBe('styleClass2'); }); @@ -217,13 +217,13 @@ class C1["Class with text label"] class C2["Class with text label"] class C3["Class with text label"]`); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class with text label'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class with text label'); - const c3 = classParser.getClass('C3'); + const c3 = classDb.getClass('C3'); expect(c3.label).toBe('Class with text label'); }); @@ -243,19 +243,19 @@ class C11["With ' single quote"] class C12["With ~!@#$%^&*()_+=-/?"] class C13["With Città foreign language"] `); - expect(classParser.getClass('C1').label).toBe('OneWord'); - expect(classParser.getClass('C2').label).toBe('With, Comma'); - expect(classParser.getClass('C3').label).toBe('With (Brackets)'); - expect(classParser.getClass('C4').label).toBe('With [Brackets]'); - expect(classParser.getClass('C5').label).toBe('With {Brackets}'); - expect(classParser.getClass('C6').label).toBe(' '); - expect(classParser.getClass('C7').label).toBe('With 1 number'); - expect(classParser.getClass('C8').label).toBe('With . period...'); - expect(classParser.getClass('C9').label).toBe('With - dash'); - expect(classParser.getClass('C10').label).toBe('With _ underscore'); - expect(classParser.getClass('C11').label).toBe("With ' single quote"); - expect(classParser.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); - expect(classParser.getClass('C13').label).toBe('With Città foreign language'); + expect(classDb.getClass('C1').label).toBe('OneWord'); + expect(classDb.getClass('C2').label).toBe('With, Comma'); + expect(classDb.getClass('C3').label).toBe('With (Brackets)'); + expect(classDb.getClass('C4').label).toBe('With [Brackets]'); + expect(classDb.getClass('C5').label).toBe('With {Brackets}'); + expect(classDb.getClass('C6').label).toBe(' '); + expect(classDb.getClass('C7').label).toBe('With 1 number'); + expect(classDb.getClass('C8').label).toBe('With . period...'); + expect(classDb.getClass('C9').label).toBe('With - dash'); + expect(classDb.getClass('C10').label).toBe('With _ underscore'); + expect(classDb.getClass('C11').label).toBe("With ' single quote"); + expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); + expect(classDb.getClass('C13').label).toBe('With Città foreign language'); }); it('should handle "note for"', function () { @@ -285,8 +285,8 @@ class C13["With Città foreign language"] Student "1" --o "1" IdCard : carries Student "1" --o "1" Bike : rides`); - expect(Object.keys(classParser.getClasses()).length).toBe(3); - expect(classParser.getClasses().Student).toMatchInlineSnapshot(` + expect(Object.keys(classDb.getClasses()).length).toBe(3); + expect(classDb.getClasses().Student).toMatchInlineSnapshot(` { "annotations": [], "cssClasses": [], @@ -305,8 +305,8 @@ class C13["With Città foreign language"] "type": "", } `); - expect(classParser.getRelations().length).toBe(2); - expect(classParser.getRelations()).toMatchInlineSnapshot(` + expect(classDb.getRelations().length).toBe(2); + expect(classDb.getRelations()).toMatchInlineSnapshot(` [ { "id1": "Student", @@ -339,8 +339,8 @@ class C13["With Città foreign language"] describe('when parsing class defined in brackets', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle member definitions', function () { @@ -405,7 +405,7 @@ class C13["With Città foreign language"] const str = 'classDiagram\n' + 'class C1["Class 1 with text label"] {\n' + '+member1\n' + '}'; parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1'); @@ -420,7 +420,7 @@ class C13["With Città foreign language"] '}'; parser.parse(str); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1'); @@ -431,8 +431,8 @@ class C13["With Città foreign language"] describe('when parsing comments', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle comments at the start', function () { @@ -521,16 +521,16 @@ foo() describe('when parsing click statements', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle href link', function () { - spyOn(classParser, 'setLink'); + spyOn(classDb, 'setLink'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" '; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); const actual = parser.yy.getClass('Class1'); expect(actual.link).toBe('google.com'); @@ -538,14 +538,14 @@ foo() }); it('should handle href link with tooltip', function () { - spyOn(classParser, 'setLink'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" "A Tooltip" '; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); const actual = parser.yy.getClass('Class1'); expect(actual.link).toBe('google.com'); @@ -554,8 +554,8 @@ foo() }); it('should handle href link with tooltip and target', function () { - spyOn(classParser, 'setLink'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -563,8 +563,8 @@ foo() 'click Class1 href "google.com" "A tooltip" _self'; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + 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'); @@ -573,30 +573,30 @@ foo() }); it('should handle function call', function () { - spyOn(classParser, 'setClickEvent'); + spyOn(classDb, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 call functionCall() '; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should handle function call with tooltip', function () { - spyOn(classParser, 'setClickEvent'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setClickEvent'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 call functionCall() "A Tooltip" '; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); + 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(classParser, 'setClickEvent'); + spyOn(classDb, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -604,7 +604,7 @@ foo() 'click Class1 call functionCall(test, test1, test2)'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith( + expect(classDb.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', 'test, test1, test2' @@ -612,8 +612,8 @@ foo() }); it('should handle function call with an arbitrary number of args and tooltip', function () { - spyOn(classParser, 'setClickEvent'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setClickEvent'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -621,19 +621,19 @@ foo() 'click Class1 call functionCall("test0", test1, test2) "A Tooltip"'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith( + expect(classDb.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', '"test0", test1, test2' ); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A Tooltip'); }); }); describe('when parsing annotations', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle class annotations', function () { @@ -696,8 +696,8 @@ foo() describe('given a class diagram with members and methods ', function () { describe('when parsing members', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle simple member declaration', function () { @@ -741,8 +741,8 @@ describe('given a class diagram with members and methods ', function () { describe('when parsing method definition', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle method definition', function () { @@ -828,8 +828,8 @@ 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 () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle generic class', function () { @@ -928,8 +928,8 @@ foo() describe('given a class diagram with relationships, ', function () { describe('when parsing basic relationships', function () { beforeEach(function () { - classParser.clear(); - parser.yy = classParser; + classDb.clear(); + parser.yy = classDb; }); it('should handle all basic relationships', function () { @@ -966,9 +966,9 @@ describe('given a class diagram with relationships, ', function () { 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(classParser.relationType.EXTENSION); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); }); it('should handle relationships with labels', function () { @@ -991,9 +991,9 @@ describe('given a class diagram with relationships, ', function () { expect(parser.yy.getClass('Class1').id).toBe('Class1'); expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classParser.relationType.EXTENSION); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); }); it('should handle relation definition of different types and directions', function () { @@ -1038,9 +1038,9 @@ describe('given a class diagram with relationships, ', function () { expect(parser.yy.getClass('Class1').id).toBe('Class1'); expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classParser.relationType.AGGREGATION); + expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classParser.lineType.DOTTED_LINE); + expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); }); it('should handle relation definitions COMPOSITION on both sides', function () { @@ -1052,9 +1052,9 @@ describe('given a class diagram with relationships, ', function () { expect(parser.yy.getClass('Class1').id).toBe('Class1'); expect(parser.yy.getClass('Class02').id).toBe('Class02'); - expect(relations[0].relation.type1).toBe(classParser.relationType.COMPOSITION); - expect(relations[0].relation.type2).toBe(classParser.relationType.COMPOSITION); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + 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 () { @@ -1068,7 +1068,7 @@ describe('given a class diagram with relationships, ', function () { 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(classParser.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); }); it('should handle relation definitions with type only on right side', function () { @@ -1081,8 +1081,8 @@ describe('given a class diagram with relationships, ', function () { 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(classParser.relationType.EXTENSION); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + 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 () { @@ -1103,12 +1103,12 @@ describe('given a class diagram with relationships, ', function () { expect(relations.length).toBe(5); - expect(relations[0].relation.type1).toBe(classParser.relationType.EXTENSION); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + 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(classParser.lineType.DOTTED_LINE); + expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE); }); it('should handle generic class with relation definitions', function () { @@ -1121,9 +1121,9 @@ describe('given a class diagram with relationships, ', function () { 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(classParser.relationType.EXTENSION); + expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION); expect(relations[0].relation.type2).toBe('none'); - expect(relations[0].relation.lineType).toBe(classParser.lineType.LINE); + expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE); }); it('should handle class annotations', function () { @@ -1286,8 +1286,8 @@ describe('given a class diagram with relationships, ', function () { }); it('should associate click and href link with tooltip and target appropriately', function () { - spyOn(classParser, 'setLink'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1295,12 +1295,12 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 href "google.com" "A tooltip" _self'; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + 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(classParser, 'setLink'); + spyOn(classDb, 'setLink'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1308,11 +1308,11 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 href "google.com"'; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com'); + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com'); }); it('should associate click and href link with target appropriately', function () { - spyOn(classParser, 'setLink'); + spyOn(classDb, 'setLink'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1320,12 +1320,12 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 href "google.com" _self'; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); }); it('should associate link appropriately', function () { - spyOn(classParser, 'setLink'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setLink'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1333,12 +1333,12 @@ describe('given a class diagram with relationships, ', function () { 'link Class1 "google.com" "A tooltip" _self'; parser.parse(str); - expect(classParser.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self'); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); it('should associate callback appropriately', function () { - spyOn(classParser, 'setClickEvent'); + spyOn(classDb, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1346,11 +1346,11 @@ describe('given a class diagram with relationships, ', function () { 'callback Class1 "functionCall"'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should associate click and call callback appropriately', function () { - spyOn(classParser, 'setClickEvent'); + spyOn(classDb, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1358,11 +1358,11 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall()'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); }); it('should associate callback appropriately with an arbitrary number of args', function () { - spyOn(classParser, 'setClickEvent'); + spyOn(classDb, 'setClickEvent'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1370,7 +1370,7 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall("test0", test1, test2)'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith( + expect(classDb.setClickEvent).toHaveBeenCalledWith( 'Class1', 'functionCall', '"test0", test1, test2' @@ -1378,8 +1378,8 @@ describe('given a class diagram with relationships, ', function () { }); it('should associate callback with tooltip', function () { - spyOn(classParser, 'setClickEvent'); - spyOn(classParser, 'setTooltip'); + spyOn(classDb, 'setClickEvent'); + spyOn(classDb, 'setTooltip'); const str = 'classDiagram\n' + 'class Class1\n' + @@ -1387,8 +1387,8 @@ describe('given a class diagram with relationships, ', function () { 'click Class1 call functionCall() "A tooltip"'; parser.parse(str); - expect(classParser.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); - expect(classParser.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); + expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); + expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip'); }); it('should add classes namespaces', function () { @@ -1413,7 +1413,7 @@ class Class2 describe('when parsing classDiagram with text labels', () => { beforeEach(function () { - parser.yy = classParser; + parser.yy = classDb; parser.yy.clear(); }); @@ -1422,9 +1422,9 @@ class Class2 class C1["Class 1 with text label"] C1 --> C2 `); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1434,9 +1434,9 @@ class Class2 class C2["Class 2 with chars @?"] C1 --> C2 `); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 with chars @?'); }); @@ -1447,12 +1447,12 @@ class Class2 } C1 --> C2 `); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); const member = c1.members[0]; expect(member.getDisplayDetails().displayText).toBe('+member1'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1464,7 +1464,7 @@ class Class2 } C1 --> C2 `); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class 1 with text label'); expect(c1.members.length).toBe(1); expect(c1.annotations.length).toBe(1); @@ -1472,7 +1472,7 @@ class Class2 const member = c1.members[0]; expect(member.getDisplayDetails().displayText).toBe('+member1'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('C2'); }); @@ -1484,7 +1484,7 @@ class C1["Class 1 with text label"]:::styleClass { C1 --> C2 `); - const c1 = classParser.getClass('C1'); + 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'); @@ -1501,7 +1501,7 @@ C1 --> C2 cssClass "C1" styleClass `); - const c1 = classParser.getClass('C1'); + 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'); @@ -1519,12 +1519,12 @@ C1 --> C2 cssClass "C1,C2" styleClass `); - const c1 = classParser.getClass('C1'); + 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 = classParser.getClass('C2'); + 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'); @@ -1539,12 +1539,12 @@ class C2["Class 2 !@#$%^&*() label"]:::styleClass2 C1 --> C2 `); - const c1 = classParser.getClass('C1'); + 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 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class 2 !@#$%^&*() label'); expect(c2.cssClasses.length).toBe(1); expect(c2.cssClasses[0]).toBe('styleClass2'); @@ -1559,13 +1559,13 @@ C1 --> C2 C3 ..> C2 `); - const c1 = classParser.getClass('C1'); + const c1 = classDb.getClass('C1'); expect(c1.label).toBe('Class with text label'); - const c2 = classParser.getClass('C2'); + const c2 = classDb.getClass('C2'); expect(c2.label).toBe('Class with text label'); - const c3 = classParser.getClass('C3'); + const c3 = classDb.getClass('C3'); expect(c3.label).toBe('Class with text label'); }); @@ -1585,19 +1585,19 @@ class C11["With ' single quote"] class C12["With ~!@#$%^&*()_+=-/?"] class C13["With Città foreign language"] `); - expect(classParser.getClass('C1').label).toBe('OneWord'); - expect(classParser.getClass('C2').label).toBe('With, Comma'); - expect(classParser.getClass('C3').label).toBe('With (Brackets)'); - expect(classParser.getClass('C4').label).toBe('With [Brackets]'); - expect(classParser.getClass('C5').label).toBe('With {Brackets}'); - expect(classParser.getClass('C6').label).toBe(' '); - expect(classParser.getClass('C7').label).toBe('With 1 number'); - expect(classParser.getClass('C8').label).toBe('With . period...'); - expect(classParser.getClass('C9').label).toBe('With - dash'); - expect(classParser.getClass('C10').label).toBe('With _ underscore'); - expect(classParser.getClass('C11').label).toBe("With ' single quote"); - expect(classParser.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); - expect(classParser.getClass('C13').label).toBe('With Città foreign language'); + expect(classDb.getClass('C1').label).toBe('OneWord'); + expect(classDb.getClass('C2').label).toBe('With, Comma'); + expect(classDb.getClass('C3').label).toBe('With (Brackets)'); + expect(classDb.getClass('C4').label).toBe('With [Brackets]'); + expect(classDb.getClass('C5').label).toBe('With {Brackets}'); + expect(classDb.getClass('C6').label).toBe(' '); + expect(classDb.getClass('C7').label).toBe('With 1 number'); + expect(classDb.getClass('C8').label).toBe('With . period...'); + expect(classDb.getClass('C9').label).toBe('With - dash'); + expect(classDb.getClass('C10').label).toBe('With _ underscore'); + expect(classDb.getClass('C11').label).toBe("With ' single quote"); + expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?'); + expect(classDb.getClass('C13').label).toBe('With Città foreign language'); }); }); }); diff --git a/packages/mermaid/src/diagrams/class/classDiagram.ts b/packages/mermaid/src/diagrams/class/classDiagram.ts index f9ae8c7090..db801a35f9 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.ts @@ -1,13 +1,13 @@ import { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: TODO Fix ts errors import parser from './parser/classDiagram.jison'; -import db from './classParser.js'; +import classDb from './classDb.js'; import styles from './styles.js'; import renderer from './classRenderer.js'; export const diagram: DiagramDefinition = { parser, - db, + db: classDb, renderer, styles, init: (cnf) => { @@ -15,6 +15,6 @@ export const diagram: DiagramDefinition = { cnf.class = {}; } cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - db.clear(); + classDb.clear(); }, }; diff --git a/packages/mermaid/src/diagrams/common/common.spec.js b/packages/mermaid/src/diagrams/common/common.spec.js index d1c68e8926..1ff91a8d17 100644 --- a/packages/mermaid/src/diagrams/common/common.spec.js +++ b/packages/mermaid/src/diagrams/common/common.spec.js @@ -69,6 +69,5 @@ describe('generic parser', function () { 'test >>' ); expect(parseGenericTypes('~test')).toEqual('~test'); - expect(parseGenericTypes('~test Array~string~')).toEqual('~test Array'); }); }); From 8435330534455a0e46a498abb4df0ac83ec0c980 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 2 Jul 2023 18:10:53 -0700 Subject: [PATCH 07/56] Update packages/mermaid/src/diagrams/class/classTypes.ts Co-authored-by: Alois Klink --- packages/mermaid/src/diagrams/class/classTypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 2835ce08a4..5eaf4fba8d 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -19,13 +19,13 @@ export interface ClassNode { export class ClassMember { id!: string; cssStyle!: string; - memberType!: string; + memberType!: 'method' | 'attribute'; visibility!: string; classifier!: string; parameters!: string; returnType!: string; - constructor(input: string, memberType: string) { + constructor(input: string, memberType: 'method' | 'attribute') { this.memberType = memberType; this.visibility = ''; this.classifier = ''; From c001520e542b56f1556a7f5eea7b4a9c815b81df Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 2 Jul 2023 18:11:25 -0700 Subject: [PATCH 08/56] Update packages/mermaid/src/diagrams/class/classTypes.ts Co-authored-by: Alois Klink --- packages/mermaid/src/diagrams/class/classTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 5eaf4fba8d..2f779a238c 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -35,7 +35,7 @@ export class ClassMember { getDisplayDetails() { let displayText = this.visibility + parseGenericTypes(this.id); if (this.memberType === 'method') { - displayText += '(' + parseGenericTypes(this.parameters.trim()) + ')'; + displayText += `(${parseGenericTypes(this.parameters.trim())})`; if (this.returnType) { displayText += ' : ' + parseGenericTypes(this.returnType); } From e29d2b29a9e5c2197abf10bcb1ce28620155bad0 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 3 Jul 2023 09:40:25 -0700 Subject: [PATCH 09/56] apply suggesitons --- demos/classchart.html | 2 +- .../src/diagrams/class/classTypes.spec.ts | 53 +++++++++++++++++++ .../mermaid/src/diagrams/common/common.ts | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/demos/classchart.html b/demos/classchart.html index 736c2e0016..98162697ee 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -43,7 +43,7 @@

Class diagram demos

} class Zebra{ +bool is_wild - +run() + +run(List~T~, List~OT~) } diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index 7ae6ae8311..4a3493e0ab 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -377,6 +377,59 @@ describe('given text representing a method, ', function () { }); }); + describe('when method parameter contains two generic', function () { + it('should parse correctly', function () { + const str = `getTimes(List~T~, List~OT~)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List'); + }); + + it('should handle public visibility', function () { + const str = `+getTimes(List~T~, List~OT~)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List, List'); + }); + + it('should handle private visibility', function () { + const str = `-getTimes(List~T~, List~OT~)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List, List'); + }); + + it('should handle protected visibility', function () { + const str = `#getTimes(List~T~, List~OT~)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List, List'); + }); + + it('should handle internal visibility', function () { + const str = `~getTimes(List~T~, List~OT~)`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List, List'); + }); + + it('should return correct css for static classifier', function () { + const str = `getTimes(List~T~, List~OT~)$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List'); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); + + it('should return correct css for abstract classifier', function () { + const str = `getTimes(List~T~, List~OT~)*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); + }); + }); + describe('when method parameter is a nested generic', function () { it('should parse correctly', function () { const str = `getTimetableList(List~List~T~~)`; diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index cf6f8cb326..d2792b3f4a 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -189,7 +189,7 @@ export const parseGenericTypes = function (text: string): string { do { cleanedText = newCleanedText; - newCleanedText = cleanedText.replace(/~([^:;]+)~/, '<$1>'); + newCleanedText = cleanedText.replace(/~([^\s:;]+)~/, '<$1>'); } while (newCleanedText != cleanedText); return parseGenericTypes(newCleanedText); From 6ed0c9bc360e9f58f9078f8eeef665ef03c34027 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 15 Aug 2023 10:02:31 -0700 Subject: [PATCH 10/56] update tests --- demos/classchart.html | 6 ++++-- .../src/diagrams/class/classDiagram.spec.ts | 16 ++++++++++++---- .../src/diagrams/class/classTypes.spec.ts | 12 ++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/demos/classchart.html b/demos/classchart.html index 98162697ee..3ad8fb100b 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -38,12 +38,14 @@

Class diagram demos

+quack() } class Fish{ - -int sizeInFeet + -Listint sizeInFeet -canEat() } class Zebra{ +bool is_wild +run(List~T~, List~OT~) + %% +run-composite(List~T, K~) + +run-nested(List~List~OT~~) } @@ -154,7 +156,7 @@

Class diagram demos

~InternalProperty : string ~AnotherInternalProperty : List~List~string~~ } - class People~List~Person~~ + class People List~List~Person~~
diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index f3568e7b85..b775521a41 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -1439,11 +1439,19 @@ class Class2 const testClasses = parser.yy.getClasses(); const testRelations = parser.yy.getRelations(); expect(Object.keys(testNamespaceA.classes).length).toBe(2); - expect(testNamespaceA.classes['A1'].members[0]).toBe('+foo : string'); - expect(testNamespaceA.classes['A2'].members[0]).toBe('+bar : int'); + expect(testNamespaceA.classes['A1'].members[0].getDisplayDetails().displayText).toBe( + '+foo : string' + ); + expect(testNamespaceA.classes['A2'].members[0].getDisplayDetails().displayText).toBe( + '+bar : int' + ); expect(Object.keys(testNamespaceB.classes).length).toBe(2); - expect(testNamespaceB.classes['B1'].members[0]).toBe('+foo : bool'); - expect(testNamespaceB.classes['B2'].members[0]).toBe('+bar : float'); + expect(testNamespaceB.classes['B1'].members[0].getDisplayDetails().displayText).toBe( + '+foo : bool' + ); + expect(testNamespaceB.classes['B2'].members[0].getDisplayDetails().displayText).toBe( + '+bar : float' + ); expect(Object.keys(testClasses).length).toBe(4); expect(testClasses['A1'].parent).toBe('A'); expect(testClasses['A2'].parent).toBe('A'); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index 4a3493e0ab..56a865fe32 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -382,42 +382,42 @@ describe('given text representing a method, ', function () { const str = `getTimes(List~T~, List~OT~)`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); }); it('should handle public visibility', function () { const str = `+getTimes(List~T~, List~OT~)`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List, List)'); }); it('should handle private visibility', function () { const str = `-getTimes(List~T~, List~OT~)`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List, List)'); }); it('should handle protected visibility', function () { const str = `#getTimes(List~T~, List~OT~)`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List, List)'); }); it('should handle internal visibility', function () { const str = `~getTimes(List~T~, List~OT~)`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List, List)'); }); it('should return correct css for static classifier', function () { const str = `getTimes(List~T~, List~OT~)$`; const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List'); + expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List, List)'); expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); }); From bbaab54ec929d3a9690342e3781963e88a1fc091 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 15 Aug 2023 11:02:06 -0700 Subject: [PATCH 11/56] add tsdoc comments --- .../mermaid/src/diagrams/class/classTypes.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index 2f779a238c..de083bdcce 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -16,13 +16,32 @@ export interface ClassNode { tooltip?: string; } +export type Visibility = '#' | '+' | '~' | '-' | ''; +export const visibilityValues = ['#', '+', '~', '-', '']; + +/** + * Parses and stores class diagram member variables/methods. + * + */ export class ClassMember { id!: string; cssStyle!: string; memberType!: 'method' | 'attribute'; - visibility!: string; + visibility!: Visibility; + /** + * denote if static or to determine which css class to apply to the node + * @defaultValue '' + */ classifier!: string; + /** + * parameters for method + * @defaultValue '' + */ parameters!: string; + /** + * return type for method + * @defaultValue '' + */ returnType!: string; constructor(input: string, memberType: 'method' | 'attribute') { @@ -57,7 +76,12 @@ export class ClassMember { const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/; const match = input.match(methodRegEx); if (match) { - this.visibility = match[1] ? match[1].trim() : ''; + const detectedVisibility = match[1] ? match[1].trim() : ''; + + if (visibilityValues.includes(detectedVisibility)) { + this.visibility = detectedVisibility as Visibility; + } + this.id = match[2].trim(); this.parameters = match[3] ? match[3].trim() : ''; potentialClassifier = match[4] ? match[4].trim() : ''; @@ -76,8 +100,8 @@ export class ClassMember { const firstChar = input.substring(0, 1); const lastChar = input.substring(length - 1); - if (firstChar.match(/[#+~-]/)) { - this.visibility = firstChar; + if (visibilityValues.includes(firstChar)) { + this.visibility = firstChar as Visibility; } if (lastChar.match(/[*?]/)) { From 3f327196fdcc6cee1d344fcfe3b5800923341182 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 15 Aug 2023 11:07:21 -0700 Subject: [PATCH 12/56] return comment --- packages/mermaid/src/diagrams/common/common.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index cb80498509..1c2f6f0755 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -188,6 +188,8 @@ export const parseGenericTypes = function (text: string): string { if (text.split('~').length - 1 >= 2) { let newCleanedText = cleanedText; + // use a do...while loop instead of replaceAll to detect recursion + // e.g. Array~Array~T~~ do { cleanedText = newCleanedText; newCleanedText = cleanedText.replace(/~([^\s:;]+)~/, '<$1>'); From 3ec32521f889529627da5a95207533d7d649f85f Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Wed, 23 Aug 2023 05:22:23 -0700 Subject: [PATCH 13/56] Update packages/mermaid/src/diagrams/class/svgDraw.js Co-authored-by: Sidharth Vinod --- packages/mermaid/src/diagrams/class/svgDraw.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index ccc79aebbe..d6ed7bca4e 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -381,8 +381,7 @@ export const drawNote = function (elem, note, conf, diagObj) { * @param {{ padding: string; textHeight: string }} conf The configuration for the member */ const addTspan = function (textEl, member, isFirst, conf) { - const displayText = member.getDisplayDetails().displayText; - const cssStyle = member.getDisplayDetails().cssStyle; + const { displayText, cssStyle } = member.getDisplayDetails(); const tSpan = textEl.append('tspan').attr('x', conf.padding).text(displayText); if (cssStyle !== '') { From 8b96282c486a288a7a4171724525f0225d0caf5c Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 25 Aug 2023 10:58:20 -0700 Subject: [PATCH 14/56] improvements to parseGenericTypes --- .../src/diagrams/class/classDiagram.spec.ts | 2 +- .../mermaid/src/diagrams/common/common.ts | 82 +++++++++++++++---- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts index 92a0cbd8b2..46b4c1b161 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.spec.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.spec.ts @@ -397,7 +397,7 @@ class C13["With Città foreign language"] { "annotations": [], "cssClasses": [], - "domId": "classId-Student-34", + "domId": "classId-Student-134", "id": "Student", "label": "Student", "members": [ diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 1c2f6f0755..418c3206db 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -178,27 +178,79 @@ export const getMin = function (...values: number[]): number { * @param text - The text to convert * @returns The converted string */ -export const parseGenericTypes = function (text: string): string { - if (!text) { - return ''; +export const parseGenericTypes = function (input: string): string { + const inputSets = input.split(/(,)/); + const output = []; + let finalResult = ''; + let skipNextSet = false; + + for (let i = 0; i < inputSets.length; i++) { + const previousIndex = i - 1; + const nextIndex = i + 1; + let thisSet = inputSets[i]; + + // based on logic below - if we have already combined this set with the previous, we want to skip it + if (skipNextSet) { + continue; + } + + // if the original input included a value such as "~K, V~"", these will be split into + // an array of ["~K",","," V~"]. + // This means that on each call of processSet, there will only be 1 ~ present + // To account for this, if we encounter a ",", we are checking the previous and next sets in the array + // to see if they contain matching ~'s + // in which case we are assuming that they should be rejoined and sent to be processed + // we are also removing + if (thisSet === ',' && previousIndex > -1 && nextIndex <= inputSets.length) { + const previousSet = inputSets[i - 1]; + const nextSet = inputSets[i + 1]; + if (shouldCombineSets(previousSet, nextSet)) { + thisSet = previousSet + ',' + nextSet; + skipNextSet = true; + // remove previous set + output.pop(); + } + } else { + skipNextSet = false; + } + + output.push(processSet(thisSet)); } - let cleanedText = text; + finalResult = output.join(''); + // one last scan to see if any sets were missed + finalResult = processSet(finalResult); + return finalResult; +}; + +const shouldCombineSets = (previousSet: string, nextSet: string): boolean => { + const prevCount = [...previousSet].reduce((count, char) => (char === '~' ? count + 1 : count), 0); + const nextCount = [...nextSet].reduce((count, char) => (char === '~' ? count + 1 : count), 0); - if (text.split('~').length - 1 >= 2) { - let newCleanedText = cleanedText; + return prevCount === 1 && nextCount === 1; +}; - // use a do...while loop instead of replaceAll to detect recursion - // e.g. Array~Array~T~~ - do { - cleanedText = newCleanedText; - newCleanedText = cleanedText.replace(/~([^\s:;]+)~/, '<$1>'); - } while (newCleanedText != cleanedText); +const processSet = (input: string): string => { + const chars = [...input]; + const tildeCount = chars.reduce((count, char) => (char === '~' ? count + 1 : count), 0); - return parseGenericTypes(newCleanedText); - } else { - return cleanedText; + // ignoring any + if (tildeCount <= 1) { + return input; + } + + let first = chars.indexOf('~'); + let last = chars.lastIndexOf('~'); + + while (first !== -1 && last !== -1 && first !== last) { + chars[first] = '<'; + chars[last] = '>'; + + first = chars.indexOf('~'); + last = chars.lastIndexOf('~'); } + + return chars.join(''); }; export default { From 3678ad4e9db0846095244ed9c3f39ed40563f008 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Fri, 25 Aug 2023 13:08:51 -0700 Subject: [PATCH 15/56] modifications to generic parser --- .../mermaid/src/diagrams/class/classDb.ts | 2 +- .../mermaid/src/diagrams/common/common.ts | 24 ++++--------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index c77bacc55f..71afa59933 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -15,6 +15,7 @@ import { setDiagramTitle, getDiagramTitle, } from '../../commonDb.js'; +import { ClassMember } from './classTypes.js'; import type { ClassRelation, ClassNode, @@ -22,7 +23,6 @@ import type { ClassMap, NamespaceMap, NamespaceNode, - ClassMember, } from './classTypes.js'; const MERMAID_DOM_ID_PREFIX = 'classId-'; diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 3814c8b580..bb9c6b649d 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -181,46 +181,31 @@ export const getMin = function (...values: number[]): number { export const parseGenericTypes = function (input: string): string { const inputSets = input.split(/(,)/); const output = []; - let finalResult = ''; - let skipNextSet = false; for (let i = 0; i < inputSets.length; i++) { - const previousIndex = i - 1; - const nextIndex = i + 1; let thisSet = inputSets[i]; - // based on logic below - if we have already combined this set with the previous, we want to skip it - if (skipNextSet) { - continue; - } - // if the original input included a value such as "~K, V~"", these will be split into // an array of ["~K",","," V~"]. // This means that on each call of processSet, there will only be 1 ~ present // To account for this, if we encounter a ",", we are checking the previous and next sets in the array // to see if they contain matching ~'s // in which case we are assuming that they should be rejoined and sent to be processed - // we are also removing - if (thisSet === ',' && previousIndex > -1 && nextIndex <= inputSets.length) { + if (thisSet === ',' && i > 0 && i + 1 < inputSets.length) { const previousSet = inputSets[i - 1]; const nextSet = inputSets[i + 1]; + if (shouldCombineSets(previousSet, nextSet)) { thisSet = previousSet + ',' + nextSet; - skipNextSet = true; - // remove previous set + i++; // Move the index forward to skip the next iteration since we're combining sets output.pop(); } - } else { - skipNextSet = false; } output.push(processSet(thisSet)); } - finalResult = output.join(''); - // one last scan to see if any sets were missed - finalResult = processSet(finalResult); - return finalResult; + return output.join(''); }; const shouldCombineSets = (previousSet: string, nextSet: string): boolean => { @@ -234,7 +219,6 @@ const processSet = (input: string): string => { const chars = [...input]; const tildeCount = chars.reduce((count, char) => (char === '~' ? count + 1 : count), 0); - // ignoring any if (tildeCount <= 1) { return input; } From fa6198b4ce59b80a24fafe54d2ad18c9c0702639 Mon Sep 17 00:00:00 2001 From: jgreywolf Date: Fri, 25 Aug 2023 20:13:40 +0000 Subject: [PATCH 16/56] Update docs --- docs/syntax/flowchart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index ee635d451a..2b92822b29 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -1098,7 +1098,7 @@ The icons are accessed via the syntax fa:#icon class name#. ```mermaid-example flowchart TD - B["fab:fa-twitter for peace"] + B["fa:fa-twitter for peace"] B-->C[fa:fa-ban forbidden] B-->D(fa:fa-spinner) B-->E(A fa:fa-camera-retro perhaps?) @@ -1106,7 +1106,7 @@ flowchart TD ```mermaid flowchart TD - B["fab:fa-twitter for peace"] + B["fa:fa-twitter for peace"] B-->C[fa:fa-ban forbidden] B-->D(fa:fa-spinner) B-->E(A fa:fa-camera-retro perhaps?) From 87880fdf4063d64d7f2d80133a2ff5c8160a60d2 Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sat, 26 Aug 2023 14:01:14 -0700 Subject: [PATCH 17/56] add sanitize text --- packages/mermaid/src/diagrams/class/classTypes.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts index de083bdcce..aa5ec7b70d 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.ts @@ -1,4 +1,5 @@ -import { parseGenericTypes } from '../common/common.js'; +import { getConfig } from '../../config.js'; +import { parseGenericTypes, sanitizeText } from '../common/common.js'; export interface ClassNode { id: string; @@ -48,7 +49,8 @@ export class ClassMember { this.memberType = memberType; this.visibility = ''; this.classifier = ''; - this.parseMember(input); + const sanitizedInput = sanitizeText(input, getConfig()); + this.parseMember(sanitizedInput); } getDisplayDetails() { From 264f7920f0360cde054d88d1d4a2a0ef2cae7910 Mon Sep 17 00:00:00 2001 From: Tom PERRILLAT-COLLOMB Date: Sun, 27 Aug 2023 00:05:12 +0200 Subject: [PATCH 18/56] fix(er): allow underscore as leading char --- demos/er.html | 15 +++++++++++++++ docs/syntax/entityRelationshipDiagram.md | 2 +- .../src/diagrams/er/parser/erDiagram.jison | 2 +- .../src/diagrams/er/parser/erDiagram.spec.js | 9 ++++++++- .../src/docs/syntax/entityRelationshipDiagram.md | 2 +- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/demos/er.html b/demos/er.html index 6b23d6b329..027c2e2772 100644 --- a/demos/er.html +++ b/demos/er.html @@ -125,6 +125,21 @@
+
+    erDiagram
+      _customer_order {
+          bigint id PK
+          bigint customer_id FK
+          text shipping_address 
+          text delivery_method 
+          timestamp_with_time_zone ordered_at 
+          numeric total_tax_amount 
+          numeric total_price 
+          text payment_method 
+      }
+    
+
+