From 0aa09bfca597a5302f2b35a9ba91fe0403da928d Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 30 May 2023 10:43:44 -0700 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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 12f11c6721b3be26374c2704083bb9821849779f Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sun, 27 Aug 2023 15:47:22 -0700 Subject: [PATCH 18/31] added test case --- packages/mermaid/src/diagrams/class/classTypes.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index 56a865fe32..b52a0860b3 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -661,4 +661,13 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle); }); }); + + describe('--uncategorized tests--', function () { + it('member name should handle double colons', function () { + const str = `std::map ~int,string~ pMap;`; + + const classMember = new ClassMember(str, 'attribute'); + expect(classMember.getDisplayDetails().displayText).toBe('std::map pMap;'); + }); + }); }); From 19aaf459a576e3173667ff04d3286a1d87d050d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 00:12:53 +0000 Subject: [PATCH 19/31] chore(deps): update all minor dependencies --- package.json | 2 +- packages/mermaid/package.json | 2 +- pnpm-lock.yaml | 38 +++++++++++++++++------------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 0bd4a309cf..cdaf8432c1 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "10.2.4", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@8.6.12", + "packageManager": "pnpm@8.7.0", "keywords": [ "diagram", "markdown", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 2c5dc68896..7b4de70a98 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -117,7 +117,7 @@ "rimraf": "^5.0.0", "start-server-and-test": "^2.0.0", "type-fest": "^4.1.0", - "typedoc": "^0.24.5", + "typedoc": "^0.25.0", "typedoc-plugin-markdown": "^3.15.2", "typescript": "^5.0.4", "unist-util-flatmap": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f65796d2..992d9fe470 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -358,11 +358,11 @@ importers: specifier: ^4.1.0 version: 4.1.0 typedoc: - specifier: ^0.24.5 - version: 0.24.5(typescript@5.0.4) + specifier: ^0.25.0 + version: 0.25.0(typescript@5.0.4) typedoc-plugin-markdown: specifier: ^3.15.2 - version: 3.15.2(typedoc@0.24.5) + version: 3.15.2(typedoc@0.25.0) typescript: specifier: ^5.0.4 version: 5.0.4 @@ -9350,7 +9350,7 @@ packages: dependencies: foreground-child: 3.1.1 jackspeak: 2.1.1 - minimatch: 9.0.0 + minimatch: 9.0.1 minipass: 5.0.0 path-scurry: 1.7.0 dev: true @@ -11974,15 +11974,15 @@ packages: brace-expansion: 2.0.1 dev: true - /minimatch@9.0.0: - resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==} + /minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 dev: true - /minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 @@ -14764,35 +14764,35 @@ packages: is-typedarray: 1.0.0 dev: true - /typedoc-plugin-markdown@3.15.2(typedoc@0.24.5): + /typedoc-plugin-markdown@3.15.2(typedoc@0.25.0): resolution: {integrity: sha512-OPXAL9hhdoVJzH/UaKAz6CBS/s8KlYyLWwnxF7ap0fQCuaMMWShA1JBq4n1SXbiGjx+7DOhOfTKQ5OzwryN3Vw==} peerDependencies: typedoc: '>=0.24.0' dependencies: handlebars: 4.7.7 - typedoc: 0.24.5(typescript@5.0.4) - typedoc-plugin-mdn-links: 3.0.3(typedoc@0.24.5) + typedoc: 0.25.0(typescript@5.0.4) + typedoc-plugin-mdn-links: 3.0.3(typedoc@0.25.0) dev: true - /typedoc-plugin-mdn-links@3.0.3(typedoc@0.24.5): + /typedoc-plugin-mdn-links@3.0.3(typedoc@0.25.0): resolution: {integrity: sha512-NXhIpwQnsg7BcyMCHVqj3tUK+DL4g3Bt96JbFl4APzTGFkA+iM6GfZ/fn3TAqJ8O0CXG5R9BfWxolw1m1omNuQ==} peerDependencies: typedoc: '>= 0.23.14 || 0.24.x' dependencies: - typedoc: 0.24.5(typescript@5.0.4) + typedoc: 0.25.0(typescript@5.0.4) dev: true - /typedoc@0.24.5(typescript@5.0.4): - resolution: {integrity: sha512-tE1YDRxOTwJ33HltVazKiADqy/CasUmd2UVMnGOS2kX5Oj7q4rDVsIlcC0j03K1h3lEkGtvEyusBF7Psz4F4kA==} - engines: {node: '>= 14.14'} + /typedoc@0.25.0(typescript@5.0.4): + resolution: {integrity: sha512-FvCYWhO1n5jACE0C32qg6b3dSfQ8f2VzExnnRboowHtqUD6ARzM2r8YJeZFYXhcm2hI4C2oCRDgNPk/yaQUN9g==} + engines: {node: '>= 16'} hasBin: true peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x dependencies: lunr: 2.3.9 marked: 4.3.0 - minimatch: 9.0.0 - shiki: 0.14.1 + minimatch: 9.0.3 + shiki: 0.14.3 typescript: 5.0.4 dev: true From 6b0e0e21ac2c08f334c176c1be4e6edf4f176911 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 05:09:59 +0000 Subject: [PATCH 20/31] chore(deps): update all patch dependencies --- packages/mermaid/src/docs/package.json | 2 +- pnpm-lock.yaml | 207 ++++++++++++++++++++----- 2 files changed, 172 insertions(+), 37 deletions(-) diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json index 94ab7a535c..df6a13235d 100644 --- a/packages/mermaid/src/docs/package.json +++ b/packages/mermaid/src/docs/package.json @@ -32,7 +32,7 @@ "unplugin-vue-components": "^0.25.0", "vite": "^4.3.9", "vite-plugin-pwa": "^0.16.0", - "vitepress": "1.0.0-rc.4", + "vitepress": "1.0.0-rc.8", "workbox-window": "^7.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12f65796d2..b33beba6c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -475,8 +475,8 @@ importers: specifier: ^0.16.0 version: 0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0) vitepress: - specifier: 1.0.0-rc.4 - version: 1.0.0-rc.4(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0) + specifier: 1.0.0-rc.8 + version: 1.0.0-rc.8(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0) workbox-window: specifier: ^7.0.0 version: 7.0.0 @@ -545,23 +545,23 @@ packages: '@algolia/autocomplete-shared': 1.8.2 dev: true - /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)(search-insights@2.6.0): + /@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1)(search-insights@2.6.0): resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)(search-insights@2.6.0) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2) + '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1)(search-insights@2.6.0) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights dev: true - /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)(search-insights@2.6.0): + /@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1)(search-insights@2.6.0): resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} peerDependencies: search-insights: '>= 1 < 3' dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1) search-insights: 2.6.0 transitivePeerDependencies: - '@algolia/client-search' @@ -579,29 +579,29 @@ packages: algoliasearch: 4.14.2 dev: true - /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2): + /@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1): resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2) + '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1) '@algolia/client-search': 4.14.2 - algoliasearch: 4.14.2 + algoliasearch: 4.19.1 dev: true /@algolia/autocomplete-shared@1.8.2: resolution: {integrity: sha512-b6Z/X4MczChMcfhk6kfRmBzPgjoPzuS9KGR4AFsiLulLNRAAqhP+xZTKtMnZGhLuc61I20d5WqlId02AZvcO6g==} dev: true - /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2): + /@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1): resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' dependencies: '@algolia/client-search': 4.14.2 - algoliasearch: 4.14.2 + algoliasearch: 4.19.1 dev: true /@algolia/cache-browser-local-storage@4.14.2: @@ -610,16 +610,32 @@ packages: '@algolia/cache-common': 4.14.2 dev: true + /@algolia/cache-browser-local-storage@4.19.1: + resolution: {integrity: sha512-FYAZWcGsFTTaSAwj9Std8UML3Bu8dyWDncM7Ls8g+58UOe4XYdlgzXWbrIgjaguP63pCCbMoExKr61B+ztK3tw==} + dependencies: + '@algolia/cache-common': 4.19.1 + dev: true + /@algolia/cache-common@4.14.2: resolution: {integrity: sha512-SbvAlG9VqNanCErr44q6lEKD2qoK4XtFNx9Qn8FK26ePCI8I9yU7pYB+eM/cZdS9SzQCRJBbHUumVr4bsQ4uxg==} dev: true + /@algolia/cache-common@4.19.1: + resolution: {integrity: sha512-XGghi3l0qA38HiqdoUY+wvGyBsGvKZ6U3vTiMBT4hArhP3fOGLXpIINgMiiGjTe4FVlTa5a/7Zf2bwlIHfRqqg==} + dev: true + /@algolia/cache-in-memory@4.14.2: resolution: {integrity: sha512-HrOukWoop9XB/VFojPv1R5SVXowgI56T9pmezd/djh2JnVN/vXswhXV51RKy4nCpqxyHt/aGFSq2qkDvj6KiuQ==} dependencies: '@algolia/cache-common': 4.14.2 dev: true + /@algolia/cache-in-memory@4.19.1: + resolution: {integrity: sha512-+PDWL+XALGvIginigzu8oU6eWw+o76Z8zHbBovWYcrtWOEtinbl7a7UTt3x3lthv+wNuFr/YD1Gf+B+A9V8n5w==} + dependencies: + '@algolia/cache-common': 4.19.1 + dev: true + /@algolia/client-account@4.14.2: resolution: {integrity: sha512-WHtriQqGyibbb/Rx71YY43T0cXqyelEU0lB2QMBRXvD2X0iyeGl4qMxocgEIcbHyK7uqE7hKgjT8aBrHqhgc1w==} dependencies: @@ -628,6 +644,14 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /@algolia/client-account@4.19.1: + resolution: {integrity: sha512-Oy0ritA2k7AMxQ2JwNpfaEcgXEDgeyKu0V7E7xt/ZJRdXfEpZcwp9TOg4TJHC7Ia62gIeT2Y/ynzsxccPw92GA==} + dependencies: + '@algolia/client-common': 4.19.1 + '@algolia/client-search': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /@algolia/client-analytics@4.14.2: resolution: {integrity: sha512-yBvBv2mw+HX5a+aeR0dkvUbFZsiC4FKSnfqk9rrfX+QrlNOKEhCG0tJzjiOggRW4EcNqRmaTULIYvIzQVL2KYQ==} dependencies: @@ -637,6 +661,15 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /@algolia/client-analytics@4.19.1: + resolution: {integrity: sha512-5QCq2zmgdZLIQhHqwl55ZvKVpLM3DNWjFI4T+bHr3rGu23ew2bLO4YtyxaZeChmDb85jUdPDouDlCumGfk6wOg==} + dependencies: + '@algolia/client-common': 4.19.1 + '@algolia/client-search': 4.19.1 + '@algolia/requester-common': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /@algolia/client-common@4.14.2: resolution: {integrity: sha512-43o4fslNLcktgtDMVaT5XwlzsDPzlqvqesRi4MjQz2x4/Sxm7zYg5LRYFol1BIhG6EwxKvSUq8HcC/KxJu3J0Q==} dependencies: @@ -644,6 +677,13 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /@algolia/client-common@4.19.1: + resolution: {integrity: sha512-3kAIVqTcPrjfS389KQvKzliC559x+BDRxtWamVJt8IVp7LGnjq+aVAXg4Xogkur1MUrScTZ59/AaUd5EdpyXgA==} + dependencies: + '@algolia/requester-common': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /@algolia/client-personalization@4.14.2: resolution: {integrity: sha512-ACCoLi0cL8CBZ1W/2juehSltrw2iqsQBnfiu/Rbl9W2yE6o2ZUb97+sqN/jBqYNQBS+o0ekTMKNkQjHHAcEXNw==} dependencies: @@ -652,6 +692,14 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /@algolia/client-personalization@4.19.1: + resolution: {integrity: sha512-8CWz4/H5FA+krm9HMw2HUQenizC/DxUtsI5oYC0Jxxyce1vsr8cb1aEiSJArQT6IzMynrERif1RVWLac1m36xw==} + dependencies: + '@algolia/client-common': 4.19.1 + '@algolia/requester-common': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /@algolia/client-search@4.14.2: resolution: {integrity: sha512-L5zScdOmcZ6NGiVbLKTvP02UbxZ0njd5Vq9nJAmPFtjffUSOGEp11BmD2oMJ5QvARgx2XbX4KzTTNS5ECYIMWw==} dependencies: @@ -660,32 +708,66 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /@algolia/client-search@4.19.1: + resolution: {integrity: sha512-mBecfMFS4N+yK/p0ZbK53vrZbL6OtWMk8YmnOv1i0LXx4pelY8TFhqKoTit3NPVPwoSNN0vdSN9dTu1xr1XOVw==} + dependencies: + '@algolia/client-common': 4.19.1 + '@algolia/requester-common': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /@algolia/logger-common@4.14.2: resolution: {integrity: sha512-/JGlYvdV++IcMHBnVFsqEisTiOeEr6cUJtpjz8zc0A9c31JrtLm318Njc72p14Pnkw3A/5lHHh+QxpJ6WFTmsA==} dev: true + /@algolia/logger-common@4.19.1: + resolution: {integrity: sha512-i6pLPZW/+/YXKis8gpmSiNk1lOmYCmRI6+x6d2Qk1OdfvX051nRVdalRbEcVTpSQX6FQAoyeaui0cUfLYW5Elw==} + dev: true + /@algolia/logger-console@4.14.2: resolution: {integrity: sha512-8S2PlpdshbkwlLCSAB5f8c91xyc84VM9Ar9EdfE9UmX+NrKNYnWR1maXXVDQQoto07G1Ol/tYFnFVhUZq0xV/g==} dependencies: '@algolia/logger-common': 4.14.2 dev: true + /@algolia/logger-console@4.19.1: + resolution: {integrity: sha512-jj72k9GKb9W0c7TyC3cuZtTr0CngLBLmc8trzZlXdfvQiigpUdvTi1KoWIb2ZMcRBG7Tl8hSb81zEY3zI2RlXg==} + dependencies: + '@algolia/logger-common': 4.19.1 + dev: true + /@algolia/requester-browser-xhr@4.14.2: resolution: {integrity: sha512-CEh//xYz/WfxHFh7pcMjQNWgpl4wFB85lUMRyVwaDPibNzQRVcV33YS+63fShFWc2+42YEipFGH2iPzlpszmDw==} dependencies: '@algolia/requester-common': 4.14.2 dev: true + /@algolia/requester-browser-xhr@4.19.1: + resolution: {integrity: sha512-09K/+t7lptsweRTueHnSnmPqIxbHMowejAkn9XIcJMLdseS3zl8ObnS5GWea86mu3vy4+8H+ZBKkUN82Zsq/zg==} + dependencies: + '@algolia/requester-common': 4.19.1 + dev: true + /@algolia/requester-common@4.14.2: resolution: {integrity: sha512-73YQsBOKa5fvVV3My7iZHu1sUqmjjfs9TteFWwPwDmnad7T0VTCopttcsM3OjLxZFtBnX61Xxl2T2gmG2O4ehg==} dev: true + /@algolia/requester-common@4.19.1: + resolution: {integrity: sha512-BisRkcWVxrDzF1YPhAckmi2CFYK+jdMT60q10d7z3PX+w6fPPukxHRnZwooiTUrzFe50UBmLItGizWHP5bDzVQ==} + dev: true + /@algolia/requester-node-http@4.14.2: resolution: {integrity: sha512-oDbb02kd1o5GTEld4pETlPZLY0e+gOSWjWMJHWTgDXbv9rm/o2cF7japO6Vj1ENnrqWvLBmW1OzV9g6FUFhFXg==} dependencies: '@algolia/requester-common': 4.14.2 dev: true + /@algolia/requester-node-http@4.19.1: + resolution: {integrity: sha512-6DK52DHviBHTG2BK/Vv2GIlEw7i+vxm7ypZW0Z7vybGCNDeWzADx+/TmxjkES2h15+FZOqVf/Ja677gePsVItA==} + dependencies: + '@algolia/requester-common': 4.19.1 + dev: true + /@algolia/transporter@4.14.2: resolution: {integrity: sha512-t89dfQb2T9MFQHidjHcfhh6iGMNwvuKUvojAj+JsrHAGbuSy7yE4BylhLX6R0Q1xYRoC4Vvv+O5qIw/LdnQfsQ==} dependencies: @@ -694,6 +776,14 @@ packages: '@algolia/requester-common': 4.14.2 dev: true + /@algolia/transporter@4.19.1: + resolution: {integrity: sha512-nkpvPWbpuzxo1flEYqNIbGz7xhfhGOKGAZS7tzC+TELgEmi7z99qRyTfNSUlW7LZmB3ACdnqAo+9A9KFBENviQ==} + dependencies: + '@algolia/cache-common': 4.19.1 + '@algolia/logger-common': 4.19.1 + '@algolia/requester-common': 4.19.1 + dev: true + /@alloc/quick-lru@5.2.0: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -2827,8 +2917,8 @@ packages: resolution: {integrity: sha512-NaXVp3I8LdmJ54fn038KHgG7HmbIzZlKS2FkVf6mKcW5bYMJovkx4947joQyZk5yubxOZ+ddHSh79y39Aevufg==} dev: true - /@docsearch/css@3.5.1: - resolution: {integrity: sha512-2Pu9HDg/uP/IT10rbQ+4OrTQuxIWdKVUEdcw9/w7kZJv9NeHS6skJx1xuRiFyoGKwAzcHXnLp7csE99sj+O1YA==} + /@docsearch/css@3.5.2: + resolution: {integrity: sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==} dev: true /@docsearch/js@3.3.5(@algolia/client-search@4.14.2): @@ -2843,10 +2933,10 @@ packages: - react-dom dev: true - /@docsearch/js@3.5.1(@algolia/client-search@4.14.2)(search-insights@2.6.0): - resolution: {integrity: sha512-EXi8de5njxgP6TV3N9ytnGRLG9zmBNTEZjR4VzwPcpPLbZxxTLG2gaFyJyKiFVQxHW/DPlMrDJA3qoRRGEkgZw==} + /@docsearch/js@3.5.2(@algolia/client-search@4.14.2)(search-insights@2.6.0): + resolution: {integrity: sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==} dependencies: - '@docsearch/react': 3.5.1(@algolia/client-search@4.14.2)(search-insights@2.6.0) + '@docsearch/react': 3.5.2(@algolia/client-search@4.14.2)(search-insights@2.6.0) preact: 10.11.0 transitivePeerDependencies: - '@algolia/client-search' @@ -2878,12 +2968,13 @@ packages: - '@algolia/client-search' dev: true - /@docsearch/react@3.5.1(@algolia/client-search@4.14.2)(search-insights@2.6.0): - resolution: {integrity: sha512-t5mEODdLzZq4PTFAm/dvqcvZFdPDMdfPE5rJS5SC8OUq9mPzxEy6b+9THIqNM9P0ocCb4UC5jqBrxKclnuIbzQ==} + /@docsearch/react@3.5.2(@algolia/client-search@4.14.2)(search-insights@2.6.0): + resolution: {integrity: sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' react-dom: '>= 16.8.0 < 19.0.0' + search-insights: '>= 1 < 3' peerDependenciesMeta: '@types/react': optional: true @@ -2891,14 +2982,16 @@ packages: optional: true react-dom: optional: true + search-insights: + optional: true dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2)(search-insights@2.6.0) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.14.2) - '@docsearch/css': 3.5.1 - algoliasearch: 4.14.2 + '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1)(search-insights@2.6.0) + '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.14.2)(algoliasearch@4.19.1) + '@docsearch/css': 3.5.2 + algoliasearch: 4.19.1 + search-insights: 2.6.0 transitivePeerDependencies: - '@algolia/client-search' - - search-insights dev: true /@es-joy/jsdoccomment@0.39.4: @@ -5378,8 +5471,20 @@ packages: - vue dev: true - /@vueuse/integrations@10.3.0(focus-trap@7.5.2)(vue@3.3.4): - resolution: {integrity: sha512-Jgiv7oFyIgC6BxmDtiyG/fxyGysIds00YaY7sefwbhCZ2/tjEx1W/1WcsISSJPNI30in28+HC2J4uuU8184ekg==} + /@vueuse/core@10.4.1(vue@3.3.4): + resolution: {integrity: sha512-DkHIfMIoSIBjMgRRvdIvxsyboRZQmImofLyOHADqiVbQVilP8VVHDhBX2ZqoItOgu7dWa8oXiNnScOdPLhdEXg==} + dependencies: + '@types/web-bluetooth': 0.0.17 + '@vueuse/metadata': 10.4.1 + '@vueuse/shared': 10.4.1(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + + /@vueuse/integrations@10.4.1(focus-trap@7.5.2)(vue@3.3.4): + resolution: {integrity: sha512-uRBPyG5Lxoh1A/J+boiioPT3ELEAPEo4t8W6Mr4yTKIQBeW/FcbsotZNPr4k9uz+3QEksMmflWloS9wCnypM7g==} peerDependencies: async-validator: '*' axios: '*' @@ -5419,8 +5524,8 @@ packages: universal-cookie: optional: true dependencies: - '@vueuse/core': 10.3.0(vue@3.3.4) - '@vueuse/shared': 10.3.0(vue@3.3.4) + '@vueuse/core': 10.4.1(vue@3.3.4) + '@vueuse/shared': 10.4.1(vue@3.3.4) focus-trap: 7.5.2 vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: @@ -5436,6 +5541,10 @@ packages: resolution: {integrity: sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==} dev: true + /@vueuse/metadata@10.4.1: + resolution: {integrity: sha512-2Sc8X+iVzeuMGHr6O2j4gv/zxvQGGOYETYXEc41h0iZXIRnRbJZGmY/QP8dvzqUelf8vg0p/yEA5VpCEu+WpZg==} + dev: true + /@vueuse/shared@10.1.0(vue@3.3.4): resolution: {integrity: sha512-2X52ogu12i9DkKOQ01yeb/BKg9UO87RNnpm5sXkQvyORlbq8ONS5l39MYkjkeVWWjdT0teJru7a2S41dmHmqjQ==} dependencies: @@ -5454,6 +5563,15 @@ packages: - vue dev: true + /@vueuse/shared@10.4.1(vue@3.3.4): + resolution: {integrity: sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==} + dependencies: + vue-demi: 0.14.5(vue@3.3.4) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + /@wdio/config@7.30.0(typescript@5.1.3): resolution: {integrity: sha512-/38rol9WCfFTMtXyd/C856/aexxIZnfVvXg7Fw2WXpqZ9qadLA+R4N35S2703n/RByjK/5XAYtHoljtvh3727w==} engines: {node: '>=12.0.0'} @@ -5856,6 +5974,25 @@ packages: '@algolia/transporter': 4.14.2 dev: true + /algoliasearch@4.19.1: + resolution: {integrity: sha512-IJF5b93b2MgAzcE/tuzW0yOPnuUyRgGAtaPv5UUywXM8kzqfdwZTO4sPJBzoGz1eOy6H9uEchsJsBFTELZSu+g==} + dependencies: + '@algolia/cache-browser-local-storage': 4.19.1 + '@algolia/cache-common': 4.19.1 + '@algolia/cache-in-memory': 4.19.1 + '@algolia/client-account': 4.19.1 + '@algolia/client-analytics': 4.19.1 + '@algolia/client-common': 4.19.1 + '@algolia/client-personalization': 4.19.1 + '@algolia/client-search': 4.19.1 + '@algolia/logger-common': 4.19.1 + '@algolia/logger-console': 4.19.1 + '@algolia/requester-browser-xhr': 4.19.1 + '@algolia/requester-common': 4.19.1 + '@algolia/requester-node-http': 4.19.1 + '@algolia/transporter': 4.19.1 + dev: true + /amdefine@1.0.1: resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} engines: {node: '>=0.4.2'} @@ -15304,17 +15441,15 @@ packages: - terser dev: true - /vitepress@1.0.0-rc.4(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0): - resolution: {integrity: sha512-JCQ89Bm6ECUTnyzyas3JENo00UDJeK8q1SUQyJYou+4Yz5BKEc/F3O21cu++DnUT2zXc0kvQ2Aj4BZCc/nioXQ==} + /vitepress@1.0.0-rc.8(@algolia/client-search@4.14.2)(@types/node@18.16.0)(search-insights@2.6.0): + resolution: {integrity: sha512-InoW3VdY0KDrFi/Nffy9hoL5wd7ScWaCiHx6im4TCCvNP01N7AF0Wq5MX+4b3HIZGS6I9b3ltbP1pGvqHP79Mg==} hasBin: true dependencies: - '@docsearch/css': 3.5.1 - '@docsearch/js': 3.5.1(@algolia/client-search@4.14.2)(search-insights@2.6.0) - '@vitejs/plugin-vue': 4.2.3(vite@4.4.9)(vue@3.3.4) + '@docsearch/css': 3.5.2 + '@docsearch/js': 3.5.2(@algolia/client-search@4.14.2)(search-insights@2.6.0) '@vue/devtools-api': 6.5.0 - '@vueuse/core': 10.3.0(vue@3.3.4) - '@vueuse/integrations': 10.3.0(focus-trap@7.5.2)(vue@3.3.4) - body-scroll-lock: 4.0.0-beta.0 + '@vueuse/core': 10.4.1(vue@3.3.4) + '@vueuse/integrations': 10.4.1(focus-trap@7.5.2)(vue@3.3.4) focus-trap: 7.5.2 mark.js: 8.11.1 minisearch: 6.1.0 From 5839e0de8702f4ffe784958830e645f87da430ed Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Mon, 28 Aug 2023 06:13:30 -0700 Subject: [PATCH 21/31] add additional test case --- packages/mermaid/src/diagrams/class/classTypes.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index b52a0860b3..2b360d4473 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -669,5 +669,15 @@ describe('given text representing a method, ', function () { const classMember = new ClassMember(str, 'attribute'); expect(classMember.getDisplayDetails().displayText).toBe('std::map pMap;'); }); + + it('member name should handle generic type', function () { + const str = `getTime~T~(this T, int seconds)$ DateTime`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTime(this T, int seconds) : DateTime' + ); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); + }); }); }); From 05c2a11e3d265d99c21a1a190be75048c1c0e985 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sun, 3 Sep 2023 12:00:32 +0530 Subject: [PATCH 22/31] chore: Align with convention --- packages/mermaid/src/diagrams/class/classDiagram-v2.ts | 6 +++--- packages/mermaid/src/diagrams/class/classDiagram.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts index fb6c77a471..ec5398d29d 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram-v2.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram-v2.ts @@ -1,13 +1,13 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: JISON doesn't support types import parser from './parser/classDiagram.jison'; -import classDb from './classDb.js'; +import db from './classDb.js'; import styles from './styles.js'; import renderer from './classRenderer-v2.js'; export const diagram: DiagramDefinition = { parser, - db: classDb, + db, renderer, styles, init: (cnf) => { @@ -15,6 +15,6 @@ export const diagram: DiagramDefinition = { cnf.class = {}; } cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - classDb.clear(); + db.clear(); }, }; diff --git a/packages/mermaid/src/diagrams/class/classDiagram.ts b/packages/mermaid/src/diagrams/class/classDiagram.ts index 25a4abc638..7f027c186e 100644 --- a/packages/mermaid/src/diagrams/class/classDiagram.ts +++ b/packages/mermaid/src/diagrams/class/classDiagram.ts @@ -1,13 +1,13 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; // @ts-ignore: JISON doesn't support types import parser from './parser/classDiagram.jison'; -import classDb from './classDb.js'; +import db from './classDb.js'; import styles from './styles.js'; import renderer from './classRenderer.js'; export const diagram: DiagramDefinition = { parser, - db: classDb, + db, renderer, styles, init: (cnf) => { @@ -15,6 +15,6 @@ export const diagram: DiagramDefinition = { cnf.class = {}; } cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute; - classDb.clear(); + db.clear(); }, }; From 06e44f5da8d6c1c1521594ce85c7488d2d84acd2 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Sun, 3 Sep 2023 10:12:13 +0200 Subject: [PATCH 23/31] fix: typos (#4801) * fix typos * fix typo * fix typo * fix typo * fix typos * fix typos * fix typos * fix typos * fix typos * fix typo * fix typo * chore: Update docs --------- Co-authored-by: Sidharth Vinod --- docs/community/code.md | 2 +- docs/community/development.md | 2 +- docs/community/docker-development.md | 2 +- docs/community/newDiagram.md | 12 ++++++------ docs/config/directives.md | 10 +++++----- docs/config/usage.md | 8 ++++---- docs/intro/getting-started.md | 4 ++-- docs/syntax/mindmap.md | 6 +++--- docs/syntax/quadrantChart.md | 8 ++++---- docs/syntax/sankey.md | 2 +- docs/syntax/sequenceDiagram.md | 4 ++-- packages/mermaid/src/docs/community/code.md | 2 +- packages/mermaid/src/docs/community/development.md | 2 +- .../mermaid/src/docs/community/docker-development.md | 2 +- packages/mermaid/src/docs/community/newDiagram.md | 12 ++++++------ packages/mermaid/src/docs/config/directives.md | 6 +++--- packages/mermaid/src/docs/config/usage.md | 8 ++++---- packages/mermaid/src/docs/intro/getting-started.md | 4 ++-- packages/mermaid/src/docs/syntax/mindmap.md | 6 +++--- packages/mermaid/src/docs/syntax/quadrantChart.md | 8 ++++---- packages/mermaid/src/docs/syntax/sankey.md | 2 +- packages/mermaid/src/docs/syntax/sequenceDiagram.md | 2 +- 22 files changed, 57 insertions(+), 57 deletions(-) diff --git a/docs/community/code.md b/docs/community/code.md index 7f4b1e1056..e77a25467e 100644 --- a/docs/community/code.md +++ b/docs/community/code.md @@ -42,7 +42,7 @@ Once the release happens we add a tag to the `release` branch and merge it with 2. Check out the `develop` branch 3. Create a new branch for your work. Please name the branch following our naming convention below. -We use the follow naming convention for branches: +We use the following naming convention for branches: ```txt [feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces] diff --git a/docs/community/development.md b/docs/community/development.md index 65ee186607..c9d8dab979 100644 --- a/docs/community/development.md +++ b/docs/community/development.md @@ -22,7 +22,7 @@ In GitHub, you first **fork** a repository when you are going to make changes an Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with. -[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentaion, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories). +[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories). [Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo) diff --git a/docs/community/docker-development.md b/docs/community/docker-development.md index 42189a88e3..9df0c8d59c 100644 --- a/docs/community/docker-development.md +++ b/docs/community/docker-development.md @@ -22,7 +22,7 @@ In GitHub, you first **fork** a repository when you are going to make changes an Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with. -[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentaion, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories). +[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories). [Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo) diff --git a/docs/community/newDiagram.md b/docs/community/newDiagram.md index 5dd616e668..b90d192b27 100644 --- a/docs/community/newDiagram.md +++ b/docs/community/newDiagram.md @@ -19,7 +19,7 @@ For instance: #### Store data found during parsing -There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call a object provided to the parser by the user of the parser. This object can be called during parsing for storing data. +There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call an object provided to the parser by the user of the parser. This object can be called during parsing for storing data. ```jison statement @@ -35,7 +35,7 @@ In the extract of the grammar above, it is defined that a call to the setTitle m > **Note** > Make sure that the `parseError` function for the parser is defined and calling `mermaid.parseError`. This way a common way of detecting parse errors is provided for the end-user. -For more info look in the example diagram type: +For more info look at the example diagram type: The `yy` object has the following function: @@ -54,7 +54,7 @@ parser.yy = db; ### Step 2: Rendering -Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather then the flowchart renderer as this is a more generic example. +Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather than the flowchart renderer as this is a more generic example. Place the renderer in the diagram folder. @@ -62,7 +62,7 @@ Place the renderer in the diagram folder. The second thing to do is to add the capability to detect the new diagram to type to the detectType in `diagram-api/detectType.ts`. The detection should return a key for the new diagram type. [This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. -For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +For example, if your new diagram uses a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. @@ -122,7 +122,7 @@ There are a few features that are common between the different types of diagrams - Themes, there is a common way to modify the styling of diagrams in Mermaid. - Comments should follow mermaid standards -Here some pointers on how to handle these different areas. +Here are some pointers on how to handle these different areas. ## Accessibility @@ -140,7 +140,7 @@ See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/ ### accessible title and description -The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) +The syntax for accessible titles and descriptions is described in [the Accessibility documentation section.](../config/accessibility.md) As a design goal, the jison syntax should be similar between the diagrams. diff --git a/docs/config/directives.md b/docs/config/directives.md index 414565d53e..943d512173 100644 --- a/docs/config/directives.md +++ b/docs/config/directives.md @@ -126,7 +126,7 @@ The following code snippet changes `theme` to `forest`: `%%{init: { "theme": "forest" } }%%` -Possible theme values are: `default`,`base`, `dark`, `forest` and `neutral`. +Possible theme values are: `default`, `base`, `dark`, `forest` and `neutral`. Default Value is `default`. Example: @@ -291,7 +291,7 @@ Let us see an example: sequenceDiagram Alice->Bob: Hello Bob, how are you? -Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion? +Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion? Alice->Bob: Good. Bob->Alice: Cool ``` @@ -300,7 +300,7 @@ Bob->Alice: Cool sequenceDiagram Alice->Bob: Hello Bob, how are you? -Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion? +Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion? Alice->Bob: Good. Bob->Alice: Cool ``` @@ -317,7 +317,7 @@ By applying that snippet to the diagram above, `wrap` will be enabled: %%{init: { "sequence": { "wrap": true, "width":300 } } }%% sequenceDiagram Alice->Bob: Hello Bob, how are you? -Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion? +Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion? Alice->Bob: Good. Bob->Alice: Cool ``` @@ -326,7 +326,7 @@ Bob->Alice: Cool %%{init: { "sequence": { "wrap": true, "width":300 } } }%% sequenceDiagram Alice->Bob: Hello Bob, how are you? -Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion? +Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion? Alice->Bob: Good. Bob->Alice: Cool ``` diff --git a/docs/config/usage.md b/docs/config/usage.md index de88beca5e..8bd34345f1 100644 --- a/docs/config/usage.md +++ b/docs/config/usage.md @@ -41,7 +41,7 @@ pnpm add mermaid **Hosting mermaid on a web page:** -> Note:This topic explored in greater depth in the [User Guide for Beginners](../intro/getting-started.md) +> Note: This topic is explored in greater depth in the [User Guide for Beginners](../intro/getting-started.md) The easiest way to integrate mermaid on a web page requires two elements: @@ -100,7 +100,7 @@ Mermaid can load multiple diagrams, in the same page. ## Enabling Click Event and Tags in Nodes -A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduce in version 8.2 as a security improvement, aimed at preventing malicious use. +A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use. **It is the site owner's responsibility to discriminate between trustworthy and untrustworthy user-bases and we encourage the use of discretion.** @@ -115,13 +115,13 @@ Values: - **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled. - **antiscript**: HTML tags in text are allowed (only script elements are removed) and click functionality is enabled. - **loose**: HTML tags in text are allowed and click functionality is enabled. -- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc. +- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevents any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc. > **Note** > This changes the default behaviour of mermaid so that after upgrade to 8.2, unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled. > **sandbox** security level is still in the beta version. -**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing . This allows clicks and tags are allowed.** +**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing. This allows clicks and tags are allowed.** **To change `securityLevel`, you have to call `mermaid.initialize`:** diff --git a/docs/intro/getting-started.md b/docs/intro/getting-started.md index b768f388b3..246e7364c6 100644 --- a/docs/intro/getting-started.md +++ b/docs/intro/getting-started.md @@ -103,7 +103,7 @@ When writing the .html file, we give two instructions inside the html code to th a. The mermaid code for the diagram we want to create. -b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process . +b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process. **a. The embedded mermaid diagram definition inside a `
`:**
 
@@ -221,4 +221,4 @@ In this example mermaid.js is referenced in `src` as a separate JavaScript file,
 
 **Comments from Knut Sveidqvist, creator of mermaid:**
 
-- In early versions of mermaid, the `