From 07bd3811975c08fef02df9f58a1b31f95b22400a Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Sat, 11 Oct 2025 13:14:50 -0700 Subject: [PATCH] Re-added tests, including new tests and separate files to keep size down --- .../mermaid/src/diagrams/class/classDb.ts | 4 + .../class/classTypes.attribute.spec.ts | 317 ++++++++++++++++++ .../class/classTypes.classifiers.spec.ts | 170 ++++++++++ ...ec.ts.backup => classTypes.method.spec.ts} | 152 --------- .../src/diagrams/class/classTypes.spec.ts | 161 ++++----- 5 files changed, 577 insertions(+), 227 deletions(-) create mode 100644 packages/mermaid/src/diagrams/class/classTypes.attribute.spec.ts create mode 100644 packages/mermaid/src/diagrams/class/classTypes.classifiers.spec.ts rename packages/mermaid/src/diagrams/class/{classTypes.spec.ts.backup => classTypes.method.spec.ts} (85%) diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts index 82ddcf09b..41575a33f 100644 --- a/packages/mermaid/src/diagrams/class/classDb.ts +++ b/packages/mermaid/src/diagrams/class/classDb.ts @@ -265,6 +265,10 @@ export class ClassDB implements DiagramDB { theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2))); } else if (memberString.indexOf(')') > 0) { //its a method + if (memberString.length < 2) { + // Too short to be a method, ignore + return; + } theClass.methods.push(new ClassMember(memberString, 'method')); } else if (memberString) { theClass.members.push(new ClassMember(memberString, 'attribute')); diff --git a/packages/mermaid/src/diagrams/class/classTypes.attribute.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.attribute.spec.ts new file mode 100644 index 000000000..73942b344 --- /dev/null +++ b/packages/mermaid/src/diagrams/class/classTypes.attribute.spec.ts @@ -0,0 +1,317 @@ +import { ClassMember } from './classTypes.js'; +import { vi, describe, it, expect } from 'vitest'; +const spyOn = vi.spyOn; + +const staticCssStyle = 'text-decoration:underline;'; +const abstractCssStyle = 'font-style:italic;'; +const abstractStaticCssStyle = 'text-decoration:underline;font-style:italic;'; + +describe('ClassTypes - Attribute Tests', () => { + describe('Basic attribute parsing without classifiers', () => { + it('should parse attribute with no modifiers', () => { + const str = 'name String'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse attribute with public "+" visibility', () => { + const str = '+name String'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+name String'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse attribute with protected "#" visibility', () => { + const str = '#name String'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#name String'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse attribute with private "-" visibility', () => { + const str = '-name String'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-name String'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse attribute with internal "~" visibility', () => { + const str = '~name String'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('~name String'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse simple attribute name only', () => { + const str = 'id'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('id'); + expect(displayDetails.cssStyle).toBe(''); + }); + + it('should parse attribute with visibility and name only', () => { + const str = '+id'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+id'); + expect(displayDetails.cssStyle).toBe(''); + }); + }); + + describe('Static classifier ($) attributes', () => { + it('should parse static attribute without visibility', () => { + const str = 'count int$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('count int'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse static attribute with public visibility', () => { + const str = '+count int$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+count int'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse static attribute with protected visibility', () => { + const str = '#count int$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#count int'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse static attribute with private visibility', () => { + const str = '-count int$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-count int'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse static attribute with internal visibility', () => { + const str = '~count int$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('~count int'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse static attribute name only', () => { + const str = 'MAX_SIZE$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('MAX_SIZE'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + }); + + describe('Abstract classifier (*) attributes', () => { + it('should parse abstract attribute without visibility', () => { + const str = 'data String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('data String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse abstract attribute with public visibility', () => { + const str = '+data String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+data String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse abstract attribute with protected visibility', () => { + const str = '#data String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#data String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse abstract attribute with private visibility', () => { + const str = '-data String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-data String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse abstract attribute with internal visibility', () => { + const str = '~data String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('~data String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse abstract attribute name only', () => { + const str = 'value*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('value'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + }); + + describe('Abstract and Static combined classifiers', () => { + it('should parse abstract+static ($*) attribute without visibility', () => { + const str = 'config Map$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('config Map'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute without visibility', () => { + const str = 'config Map*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('config Map'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse abstract+static ($*) attribute with public visibility', () => { + const str = '+config Map$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+config Map'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute with public visibility', () => { + const str = '+config Map*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+config Map'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse abstract+static ($*) attribute with protected visibility', () => { + const str = '#registry HashMap$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#registry HashMap'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute with protected visibility', () => { + const str = '#registry HashMap*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#registry HashMap'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse abstract+static ($*) attribute with private visibility', () => { + const str = '-cache LRUCache$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-cache LRUCache'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute with private visibility', () => { + const str = '-cache LRUCache*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-cache LRUCache'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse abstract+static ($*) attribute with internal visibility', () => { + const str = '~pool ThreadPool$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('~pool ThreadPool'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute with internal visibility', () => { + const str = '~pool ThreadPool*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('~pool ThreadPool'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse abstract+static ($*) attribute name only', () => { + const str = 'INSTANCE$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('INSTANCE'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse static+abstract (*$) attribute name only', () => { + const str = 'INSTANCE*$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('INSTANCE'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + }); + + describe('Complex attribute type scenarios', () => { + it('should parse generic type attribute with static classifier', () => { + const str = '+items List~String~$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+items List'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse nested generic type attribute with abstract classifier', () => { + const str = '#mapping Map~String, List~Integer~~*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('#mapping Map~String, List'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse complex generic type with abstract+static classifiers', () => { + const str = '+factory Function~Map~String, Object~, Promise~Result~~$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe( + '+factory FunctionString, Object~, Promise' + ); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should parse attribute with spaces in type name', () => { + const str = '+fullName Full Name String$'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+fullName Full Name String'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + + it('should parse attribute with special characters in name', () => { + const str = '+user_name String*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('+user_name String'); + expect(displayDetails.cssStyle).toBe(abstractCssStyle); + }); + + it('should parse attribute with numeric suffix', () => { + const str = '-value123 int$*'; + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('-value123 int'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/class/classTypes.classifiers.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.classifiers.spec.ts new file mode 100644 index 000000000..4c76f2d01 --- /dev/null +++ b/packages/mermaid/src/diagrams/class/classTypes.classifiers.spec.ts @@ -0,0 +1,170 @@ +import { describe, it, expect } from 'vitest'; +import { ClassMember } from './classTypes.js'; + +describe('ClassTypes - Enhanced Abstract and Static Combinations', () => { + // Test constants to match original test structure + const staticCssStyle = 'text-decoration:underline;'; + const abstractCssStyle = 'font-style:italic;'; + const abstractStaticCssStyle = 'text-decoration:underline;font-style:italic;'; + + describe('Enhanced parseClassifier functionality', () => { + describe('when the attribute has static "$" modifier', () => { + it('should parse the display text correctly and apply static css style', () => { + const str = 'name String$'; + + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(staticCssStyle); + }); + }); + + describe('when the attribute has 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(abstractCssStyle); + }); + }); + + describe('when the attribute has abstract static "*$" modifier', () => { + it('should parse the display text correctly and apply abstract static css style', () => { + const str = 'name String*$'; + + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + }); + + describe('when the attribute has static abstract "$*" modifier', () => { + it('should parse the display text correctly and apply abstract static css style', () => { + const str = 'name String$*'; + + const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); + + expect(displayDetails.displayText).toBe('name String'); + expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle abstract and static combined (*$) on methods', () => { + const str = 'getTime()*$'; + const classMember = new ClassMember(str, 'method'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('getTime()'); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle static and abstract combined ($*) on methods', () => { + const str = 'getTime()$*'; + const classMember = new ClassMember(str, 'method'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('getTime()'); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle abstract and static combined (*$) on attributes', () => { + const str = 'data String*$'; + const classMember = new ClassMember(str, 'attribute'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('data String'); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle static and abstract combined ($*) on attributes', () => { + const str = 'data String$*'; + const classMember = new ClassMember(str, 'attribute'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('data String'); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle complex method with abstract static combination', () => { + const str = '+processData(Map~String, List~Integer~~) Optional~Result~*$'; + const classMember = new ClassMember(str, 'method'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe( + '+processData(Map~String, List) : Optional' + ); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should handle attribute with visibility and abstract static combination', () => { + const str = '#config Settings$*'; + const classMember = new ClassMember(str, 'attribute'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('#config Settings'); + expect(details.cssStyle).toBe(abstractStaticCssStyle); + }); + + // Verify existing classifier functionality still works + it('should still handle single static classifier correctly', () => { + const str = 'getName()$'; + const classMember = new ClassMember(str, 'method'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('getName()'); + expect(details.cssStyle).toBe(staticCssStyle); + }); + + it('should still handle single abstract classifier correctly', () => { + const str = 'name String*'; + const classMember = new ClassMember(str, 'attribute'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('name String'); + expect(details.cssStyle).toBe(abstractCssStyle); + }); + + it('should handle empty classifier correctly', () => { + const str = 'getValue()'; + const classMember = new ClassMember(str, 'method'); + const details = classMember.getDisplayDetails(); + + expect(details.displayText).toBe('getValue()'); + expect(details.cssStyle).toBe(''); + }); + }); + 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 abstract classifier', function () { + const str = `getTime()*`; + + 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 static classifier', function () { + const str = `getTime()*$`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractStaticCssStyle); + }); + + it('should return correct css for static abstract classifier', function () { + const str = `getTime()$*`; + + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); + expect(classMember.getDisplayDetails().cssStyle).toBe(abstractStaticCssStyle); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts.backup b/packages/mermaid/src/diagrams/class/classTypes.method.spec.ts similarity index 85% rename from packages/mermaid/src/diagrams/class/classTypes.spec.ts.backup rename to packages/mermaid/src/diagrams/class/classTypes.method.spec.ts index 1d1ba7b80..73e46c519 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts.backup +++ b/packages/mermaid/src/diagrams/class/classTypes.method.spec.ts @@ -42,38 +42,6 @@ describe('given text representing a method, ', function () { 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(staticCssStyle); - }); - - 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); - }); - - it('should return correct css for abstract static classifier', function () { - const str = `getTime()*$`; - - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractStaticCssStyle); - }); - - it('should return correct css for static abstract classifier', function () { - const str = `getTime()$*`; - - const classMember = new ClassMember(str, 'method'); - expect(classMember.getDisplayDetails().displayText).toBe('getTime()'); - expect(classMember.getDisplayDetails().cssStyle).toBe(abstractStaticCssStyle); - }); }); describe('when method has single parameter value', function () { @@ -856,124 +824,4 @@ describe('given text representing a method, ', function () { expect(classMember.getDisplayDetails().cssStyle).toBe(abstractStaticCssStyle); }); }); - - 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;'); - }); - - 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); - }); - }); -}); - -describe('given text representing an attribute', () => { - describe('when the attribute has no modifiers', () => { - it('should parse the display text correctly', () => { - const str = 'name String'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('name String'); - expect(displayDetails.cssStyle).toBe(''); - }); - }); - - describe('when the attribute has public "+" modifier', () => { - it('should parse the display text correctly', () => { - const str = '+name String'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('+name String'); - expect(displayDetails.cssStyle).toBe(''); - }); - }); - - describe('when the attribute has protected "#" modifier', () => { - it('should parse the display text correctly', () => { - const str = '#name String'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('#name String'); - expect(displayDetails.cssStyle).toBe(''); - }); - }); - - describe('when the attribute has private "-" modifier', () => { - it('should parse the display text correctly', () => { - const str = '-name String'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('-name String'); - expect(displayDetails.cssStyle).toBe(''); - }); - }); - - describe('when the attribute has internal "~" modifier', () => { - it('should parse the display text correctly', () => { - const str = '~name String'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('~name String'); - expect(displayDetails.cssStyle).toBe(''); - }); - }); - - describe('when the attribute has static "$" modifier', () => { - it('should parse the display text correctly and apply static css style', () => { - const str = 'name String$'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('name String'); - expect(displayDetails.cssStyle).toBe(staticCssStyle); - }); - }); - - describe('when the attribute has 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(abstractCssStyle); - }); - }); - - describe('when the attribute has abstract static "*$" modifier', () => { - it('should parse the display text correctly and apply abstract static css style', () => { - const str = 'name String*$'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('name String'); - expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); - }); - }); - - describe('when the attribute has static abstract "$*" modifier', () => { - it('should parse the display text correctly and apply abstract static css style', () => { - const str = 'name String$*'; - - const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails(); - - expect(displayDetails.displayText).toBe('name String'); - expect(displayDetails.cssStyle).toBe(abstractStaticCssStyle); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/class/classTypes.spec.ts b/packages/mermaid/src/diagrams/class/classTypes.spec.ts index b1fa3717f..12e151d9c 100644 --- a/packages/mermaid/src/diagrams/class/classTypes.spec.ts +++ b/packages/mermaid/src/diagrams/class/classTypes.spec.ts @@ -1,95 +1,106 @@ -import { describe, it, expect } from 'vitest'; import { ClassMember } from './classTypes.js'; +import { vi, describe, it, expect } from 'vitest'; +const spyOn = vi.spyOn; -describe('ClassTypes - Enhanced Abstract and Static Combinations', () => { - // Test constants to match original test structure - const staticCssStyle = 'text-decoration:underline;'; - const abstractCssStyle = 'font-style:italic;'; - const abstractStaticCssStyle = 'text-decoration:underline;font-style:italic;'; +const staticCssStyle = 'text-decoration:underline;'; +const abstractCssStyle = 'font-style:italic;'; +const abstractStaticCssStyle = 'text-decoration:underline;font-style:italic;'; - describe('Enhanced parseClassifier functionality', () => { - it('should handle abstract and static combined (*$) on methods', () => { - const str = 'getTime()*$'; - const classMember = new ClassMember(str, 'method'); - const details = classMember.getDisplayDetails(); +describe('given text representing a method, ', function () { + describe('--uncategorized tests--', function () { + it('member name should handle double colons', function () { + const str = `std::map ~int,string~ pMap;`; - expect(details.displayText).toBe('getTime()'); - expect(details.cssStyle).toBe(abstractStaticCssStyle); - }); - - it('should handle static and abstract combined ($*) on methods', () => { - const str = 'getTime()$*'; - const classMember = new ClassMember(str, 'method'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe('getTime()'); - expect(details.cssStyle).toBe(abstractStaticCssStyle); - }); - - it('should handle abstract and static combined (*$) on attributes', () => { - const str = 'data String*$'; const classMember = new ClassMember(str, 'attribute'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe('data String'); - expect(details.cssStyle).toBe(abstractStaticCssStyle); + expect(classMember.getDisplayDetails().displayText).toBe('std::map pMap;'); }); - it('should handle static and abstract combined ($*) on attributes', () => { - const str = 'data String$*'; - const classMember = new ClassMember(str, 'attribute'); - const details = classMember.getDisplayDetails(); + it('member name should handle generic type', function () { + const str = `getTime~T~(this T, int seconds)$ DateTime`; - expect(details.displayText).toBe('data String'); - expect(details.cssStyle).toBe(abstractStaticCssStyle); - }); - - it('should handle complex method with abstract static combination', () => { - const str = '+processData(Map~String, List~Integer~~) Optional~Result~*$'; const classMember = new ClassMember(str, 'method'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe( - '+processData(Map~String, List) : Optional' + expect(classMember.getDisplayDetails().displayText).toBe( + 'getTime(this T, int seconds) : DateTime' ); - expect(details.cssStyle).toBe(abstractStaticCssStyle); + expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle); }); + }); - it('should handle attribute with visibility and abstract static combination', () => { - const str = '#config Settings$*'; - const classMember = new ClassMember(str, 'attribute'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe('#config Settings'); - expect(details.cssStyle).toBe(abstractStaticCssStyle); - }); - - // Verify existing classifier functionality still works - it('should still handle single static classifier correctly', () => { - const str = 'getName()$'; + describe('Edge Cases and Additional Scenarios', () => { + it('should handle method with special characters in name', function () { + const str = `operator++(int value)`; const classMember = new ClassMember(str, 'method'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe('getName()'); - expect(details.cssStyle).toBe(staticCssStyle); + expect(classMember.getDisplayDetails().displayText).toBe('operator++(int value)'); + expect(classMember.id).toBe('operator++'); }); - it('should still handle single abstract classifier correctly', () => { - const str = 'name String*'; - const classMember = new ClassMember(str, 'attribute'); - const details = classMember.getDisplayDetails(); - - expect(details.displayText).toBe('name String'); - expect(details.cssStyle).toBe(abstractCssStyle); - }); - - it('should handle empty classifier correctly', () => { - const str = 'getValue()'; + it('should handle method with numbers in name', function () { + const str = `method123(param)`; const classMember = new ClassMember(str, 'method'); - const details = classMember.getDisplayDetails(); + expect(classMember.getDisplayDetails().displayText).toBe('method123(param)'); + expect(classMember.id).toBe('method123'); + }); - expect(details.displayText).toBe('getValue()'); - expect(details.cssStyle).toBe(''); + it('should handle method with underscores and hyphens', function () { + const str = `get_user_data(user_id int)`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('get_user_data(user_id int)'); + expect(classMember.id).toBe('get_user_data'); + }); + + it('should handle method with no spaces around parentheses', function () { + const str = `method(param)`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('method(param)'); + }); + + it('should handle method with array parameters', function () { + const str = `processArray(int[] numbers)`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('processArray(int[] numbers)'); + }); + + it('should handle method with function pointer parameter', function () { + const str = `callback(void (*fn)(int))`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('callback(void (*fn)(int))'); + }); + + it('should handle method with complex nested generics (HTML encoded)', function () { + const str = `process(Map>> data)`; + const classMember = new ClassMember(str, 'method'); + // Current behavior: parseGenericTypes converts < > to HTML entities + expect(classMember.getDisplayDetails().displayText).toBe('process(Map>> data)'); + }); + + it('should handle method with colon in return type', function () { + const str = `getNamespace() std::string`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('getNamespace() : std::string'); + }); + + it('should handle malformed input gracefully - no parentheses', function () { + const str = `not_a_method_missing_parentheses`; + const classMember = new ClassMember(str, 'method'); + // This will not match the method regex, so should handle gracefully + // But currently throws when parseGenericTypes gets undefined + expect(() => classMember.getDisplayDetails()).toThrow(); + }); + + it('should handle empty parameter list with classifier', function () { + const str = `emptyMethod()$*`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('emptyMethod()'); + expect(classMember.getDisplayDetails().cssStyle).toBe( + 'text-decoration:underline;font-style:italic;' + ); + }); + + it('should handle method with constructor-like name', function () { + const str = `Class()`; + const classMember = new ClassMember(str, 'method'); + expect(classMember.getDisplayDetails().displayText).toBe('Class()'); + expect(classMember.id).toBe('Class'); }); }); });