diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 3247a45414..3ca5d7f511 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -164,7 +164,31 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('5: should render a simple class diagram with Generic class', () => { + it('5: should render a simple class diagram with abstract method', () => { + imgSnapshotTest( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : someMethod()* + `, + {} + ); + cy.get('svg'); + }); + + it('6: should render a simple class diagram with static method', () => { + imgSnapshotTest( + ` + classDiagram + Class01 <|-- AveryLongClass : Cool + Class01 : someMethod()$ + `, + {} + ); + cy.get('svg'); + }); + + it('7: should render a simple class diagram with Generic class', () => { imgSnapshotTest( ` classDiagram @@ -184,7 +208,7 @@ describe('Class diagram', () => { cy.get('svg'); }); - it('6: should render a simple class diagram with Generic class and relations', () => { + it('8: should render a simple class diagram with Generic class and relations', () => { imgSnapshotTest( ` classDiagram diff --git a/docs/classDiagram.md b/docs/classDiagram.md index 6e838c6cd1..dc61f79cf7 100644 --- a/docs/classDiagram.md +++ b/docs/classDiagram.md @@ -105,17 +105,10 @@ Naming convention: a class name should be composed of alphanumeric (unicode allo UML provides mechanisms to represent class members, such as attributes and methods, and additional information about them. -#### Visibility -To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but is it optional: +Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and others as attributes. -- `+` Public -- `-` Private -- `#` Protected -- `~` Package -Mermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The one with `()` are treated as functions/methods, and others as attributes. - -There are two ways to define the members of a class, and regardless of the whichever syntax is used to define the members, the output will still be same. The two different ways are : +There are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are : - Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example: ``` @@ -125,7 +118,7 @@ There are two ways to define the members of a class, and regardless of the which BankAccount : +deposit(amount) BankAccount : +withdrawl(amount) ``` - ```mermaid + ``` mermaid classDiagram class BankAccount BankAccount : +String owner @@ -150,7 +143,22 @@ class BankAccount{ +BigDecimal balance +deposit(amount) +withdrawl(amount) -}``` +} +``` + + +#### Visibility +To specify the visibility of a class member (i.e. any attribute or method), these notations may be placed before the member's name, but it is optional: + +- `+` Public +- `-` Private +- `#` Protected +- `~` Package + +>_note_ you can also include additional _classifers_ to a method definition by adding the following notations to the end of the method, i.e.: after the `()`: +> - `*` Abstract e.g.: `someAbstractMethod()*` +> - `$` Static e.g.: `someStaticMethod()$` + ## Defining Relationship A relationship is a general term covering the specific types of logical connections found on class and object diagrams. diff --git a/src/diagrams/class/classDb.js b/src/diagrams/class/classDb.js index 6b6f256a59..113da1deed 100644 --- a/src/diagrams/class/classDb.js +++ b/src/diagrams/class/classDb.js @@ -94,7 +94,7 @@ export const addMember = function(className, member) { if (memberString.startsWith('<<') && memberString.endsWith('>>')) { // Remove leading and trailing brackets theClass.annotations.push(memberString.substring(2, memberString.length - 2)); - } else if (memberString.endsWith(')')) { + } else if (memberString.indexOf(')') > 0) { theClass.methods.push(memberString); } else if (memberString) { theClass.members.push(memberString); diff --git a/src/diagrams/class/classDiagram.spec.js b/src/diagrams/class/classDiagram.spec.js index 47e983dd6f..3140a41a7b 100644 --- a/src/diagrams/class/classDiagram.spec.js +++ b/src/diagrams/class/classDiagram.spec.js @@ -442,5 +442,27 @@ describe('class diagram, ', function () { expect(testClass.methods[0]).toBe('test()'); expect(testClass.methods[1]).toBe('foo()'); }); + + it('should handle abstract methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*'; + parser.parse(str); + + const testClass = parser.yy.getClass('Class1'); + expect(testClass.annotations.length).toBe(0); + expect(testClass.members.length).toBe(0); + expect(testClass.methods.length).toBe(1); + expect(testClass.methods[0]).toBe('someMethod()*'); + }); + + it('should handle static methods', function () { + const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$'; + parser.parse(str); + + const testClass = parser.yy.getClass('Class1'); + expect(testClass.annotations.length).toBe(0); + expect(testClass.members.length).toBe(0); + expect(testClass.methods.length).toBe(1); + expect(testClass.methods[0]).toBe('someMethod()$'); + }); }); }); diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js index 5287c5ee97..58f78ac34e 100644 --- a/src/diagrams/class/classRenderer.js +++ b/src/diagrams/class/classRenderer.js @@ -281,10 +281,34 @@ const drawClass = function(elem, classDef) { logger.info('Rendering class ' + classDef); const addTspan = function(textEl, txt, isFirst) { + let displayText = txt; + let cssStyle = ''; + let methodEnd = txt.indexOf(')') + 1; + + if (methodEnd > 1 && methodEnd <= txt.length) { + let classifier = txt.substring(methodEnd); + + switch (classifier) { + case '*': + cssStyle = 'font-style:italic;'; + break; + case '$': + cssStyle = 'text-decoration:underline;'; + break; + } + + displayText = txt.substring(0, methodEnd); + } + const tSpan = textEl .append('tspan') .attr('x', conf.padding) - .text(txt); + .text(displayText); + + if (cssStyle !== '') { + tSpan.attr('style', cssStyle); + } + if (!isFirst) { tSpan.attr('dy', conf.textHeight); }