Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug(ClassDiagram): Fixed a combination abstract and static modifiers not working. #5462

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/syntax/classDiagram.md
Expand Up @@ -285,8 +285,9 @@ To describe the visibility (or encapsulation) of an attribute or method/function

> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
>
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
> - `$*` Both e.g: `someAbstractMethod()$*` or `someAbstractMethod() int$*`

> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
>
Expand Down
216 changes: 216 additions & 0 deletions packages/mermaid/src/diagrams/class/classTypes.spec.ts
Expand Up @@ -4,6 +4,7 @@ const spyOn = vi.spyOn;

const staticCssStyle = 'text-decoration:underline;';
const abstractCssStyle = 'font-style:italic;';
const staticAbstractCssStyle = 'text-decoration:underline;font-style:italic;';

describe('given text representing a method, ', function () {
describe('when method has no parameters', function () {
Expand Down Expand Up @@ -57,6 +58,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime()$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTime()*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method has single parameter value', function () {
Expand Down Expand Up @@ -110,6 +127,21 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime(int)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
it('should return correct css for abstract and static classifier', function () {
const str = `getTime(int)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method has single parameter type and name (type first)', function () {
Expand Down Expand Up @@ -163,6 +195,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime(int count)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

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(int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method has single parameter type and name (name first)', function () {
Expand Down Expand Up @@ -216,6 +264,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime(count int)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTime(count int)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method has multiple parameters', function () {
Expand Down Expand Up @@ -269,6 +333,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime(string text, int count)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTime(string text, int count)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method has return type', function () {
Expand Down Expand Up @@ -322,6 +402,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTime() DateTime$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and 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(staticAbstractCssStyle);
});
});

describe('when method parameter is generic', function () {
Expand Down Expand Up @@ -375,6 +471,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTimes(List~T~)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTimes(List~T~)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method parameter contains two generic', function () {
Expand Down Expand Up @@ -428,6 +540,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTimes(List~T~, List~OT~)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTimes(List~T~, List~OT~)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method parameter is a nested generic', function () {
Expand Down Expand Up @@ -481,6 +609,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTimetableList(List~List~T~~)$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTimetableList(List~List~T~~)*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method parameter is a composite generic', function () {
Expand Down Expand Up @@ -540,6 +684,20 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = methodNameAndParameters + '$*';
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = methodNameAndParameters + '*$';
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method return type is generic', function () {
Expand Down Expand Up @@ -593,6 +751,22 @@ describe('given text representing a method, ', function () {
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTimes() List~T~$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTimes() List~T~*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when method return type is a nested generic', function () {
Expand Down Expand Up @@ -660,6 +834,26 @@ describe('given text representing a method, ', function () {
);
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});

it('should return correct css for static and abstract classifier', function () {
const str = `getTimetableList() List~List~T~~$*`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTimetableList() : List<List<T>>'
);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});

it('should return correct css for abstract and static classifier', function () {
const str = `getTimetableList() List~List~T~~*$`;

const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTimetableList() : List<List<T>>'
);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('--uncategorized tests--', function () {
Expand Down Expand Up @@ -759,4 +953,26 @@ describe('given text representing an attribute', () => {
expect(displayDetails.cssStyle).toBe(abstractCssStyle);
});
});

describe('when the attribute has static and abstract "$*" modifier', () => {
it('should parse the display text correctly and apply abstract css style', () => {
const str = 'name String$*';

const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();

expect(displayDetails.displayText).toBe('name String');
expect(displayDetails.cssStyle).toBe(staticAbstractCssStyle);
});
});

describe('when the attribute has abstract and static "*$" modifier', () => {
it('should parse the display text correctly and apply abstract css style', () => {
const str = 'name String*$';

const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();

expect(displayDetails.displayText).toBe('name String');
expect(displayDetails.cssStyle).toBe(staticAbstractCssStyle);
});
});
});
42 changes: 14 additions & 28 deletions packages/mermaid/src/diagrams/class/classTypes.ts
Expand Up @@ -76,56 +76,42 @@ export class ClassMember {
let potentialClassifier = '';

if (this.memberType === 'method') {
const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/;
const methodRegEx = /([#+~-])?(.+)\((.*)\)([$*]{0,2})(.*?)([$*]{0,2})$/;
const match = input.match(methodRegEx);
if (match) {
const detectedVisibility = match[1] ? match[1].trim() : '';

if (visibilityValues.includes(detectedVisibility)) {
this.visibility = detectedVisibility as Visibility;
}

this.visibility = (match[1] ? match[1].trim() : '') as Visibility;
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);
}
potentialClassifier = match[6] ? match[6].trim() : '';
}
}
} else {
const length = input.length;
const firstChar = input.substring(0, 1);
const lastChar = input.substring(length - 1);
const fieldRegEx = /([#+~-])?(.*?)([$*]{0,2})$/;
const match = input.match(fieldRegEx);

if (visibilityValues.includes(firstChar)) {
this.visibility = firstChar as Visibility;
}

if (lastChar.match(/[$*]/)) {
potentialClassifier = lastChar;
if (match) {
this.visibility = (match[1] ? match[1].trim() : '') as Visibility;
this.id = match[2] ? match[2].trim() : '';
potentialClassifier = match[3] ? match[3].trim() : '';
}

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;';
case '*':
return 'font-style:italic;';
case '$*':
case '*$':
return 'text-decoration:underline;font-style:italic;';
default:
return '';
}
Expand Down