Skip to content

Commit

Permalink
Fix unit test, add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
teyc committed Dec 20, 2024
1 parent 50cdec5 commit a3fb490
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 42 deletions.
2 changes: 1 addition & 1 deletion packages/mermaid/scripts/docs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ This Markdown should be kept.
expect(buildShapeDoc()).toMatchInlineSnapshot(`
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Actor | Actor | \`actor\` | Actor used in Use Cases | |
| Actor | Actor | \`actor\` | Actor used in Use Cases | \`stickman\` |
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
Expand Down
28 changes: 21 additions & 7 deletions packages/mermaid/src/diagrams/usecase/parser/usecase.jison
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
%options case-insensitive

// Special states for recognizing aliases and notes
%x ALIAS NOTE
%x ALIAS NOTE ID

%%

\s+ /* skip whitespace */
"usecase-beta" return 'DECLARATION';
"actor" return 'ACTOR';
"service" return 'SERVICE';
"actor" { this.begin('ID'); return 'ACTOR'; }
"service" { this.begin('ID'); return 'SERVICE'; }
"systemboundary" return 'SYSTEMBOUNDARY';
"-"\s[^#\n;]+ return 'TASK';
"title"\s[^#\n;]+ return 'TITLE';
Expand All @@ -28,6 +28,18 @@
"->" return 'SOLID_ARROW';
"-" return '-';
"end" return 'END';
<ID>(.+)\s+as\s+([^\n]+) {
var matches = yytext.match(/\s*(.+)\s+as\s+([^\n]+)/);
this.popState();
yytext = [matches[1], matches[2]];
return 'IDENTIFIER_AS';
}
<ID>([^\n]+) {
var matches = yytext.match(/\s*([^\n]+)/);
this.popState();
yytext = matches[1];
return 'IDENTIFIER';
}
\([^)\n]*\)\s+as\s+\([^)\n]*\) return 'USECASE_AS';
\([^)\n]*\) return 'USECASE'
"(" return '(';
Expand Down Expand Up @@ -61,13 +73,15 @@ optional_sections
;

participant_definitions
: participant_definitions participant_definition { yy.addParticipants($2); }
| participant_definition
: participant_definitions participant_definition { yy.addParticipant($2); }
| participant_definition { yy.addParticipant($1); }
;

participant_definition
: 'SERVICE' IDENTIFIER { $$ = {'service': $2} }
| 'ACTOR' IDENTIFIER { $$ = {'actor' : $2} }
: SERVICE IDENTIFIER { $$ = {'service': $2} }
| ACTOR IDENTIFIER { $$ = {'actor' : $2} }
| SERVICE IDENTIFIER_AS { $$ = {'service': $2[0], 'as': $2[1]} }
| ACTOR IDENTIFIER_AS { $$ = {'actor' : $2[0], 'as': $2[1]} }
;

systemboundary_definitions
Expand Down
22 changes: 22 additions & 0 deletions packages/mermaid/src/diagrams/usecase/parser/usecase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,27 @@ describe('Usecase diagram', function () {
// console.log(usecase.yy.getRelationships());
// console.log(usecase.yy.getSystemBoundaries());
});

it('should handle aliases', function () {
const input = `
usecase-beta
actor SU as Student Union
actor University Chancellor
service PMS as Payment Management System
SU -> (Pay Dues) -> PMS
`;
const result = usecase.parse(prepareTextForParsing(input));
const db = usecase.yy as UsecaseDB;
expect(result).toBeTruthy();
expect(db.getActors()).toStrictEqual(['SU', 'University Chancellor']);
expect(db.getServices()).toStrictEqual(['PMS']);
expect(db.getData().nodes.find((n) => n.id === 'SU')?.label).toEqual('Student Union');
expect(db.getData().nodes.find((n) => n.id === 'University Chancellor')?.label).toEqual(
'University Chancellor'
);
expect(db.getData().nodes.find((n) => n.id === 'PMS')?.label).toEqual(
'Payment Management System'
);
});
});
});
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/usecase/usecase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('Usecase diagram', function () {
db.clear();
});
it('should add node when adding participants', function () {
db.addParticipants({ service: 'Authz' });
db.addParticipant({ service: 'Authz' });
expect(db.getServices()).toStrictEqual(['Authz']);
});
it('should add actor when adding relationship', function () {
Expand Down
62 changes: 29 additions & 33 deletions packages/mermaid/src/diagrams/usecase/usecaseDB.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { BaseDiagramConfig, MermaidConfig } from '../../config.type.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { log } from '../../logger.js';

import { log, setLogLevel } from '../../logger.js';
import type { LayoutData } from '../../mermaid.js';
import type { Edge, Node } from '../../rendering-util/types.js';
import common from '../common/common.js';
Expand All @@ -26,7 +26,6 @@ function isUseCaseLabel(label: string | undefined) {
export class UsecaseDB {
private aliases = new Map<string, string>();
private links: UsecaseLink[] = [];
private nodes: UsecaseNode[] = [];
private nodesMap = new Map<string, UsecaseNode>();
private systemBoundaries: UsecaseSystemBoundary[] = [];

Expand All @@ -37,7 +36,6 @@ export class UsecaseDB {
clear(): void {
this.aliases = new Map();
this.links = [];
this.nodes = [];
this.nodesMap = new Map();
this.systemBoundaries = [];
commonClear();
Expand All @@ -49,14 +47,14 @@ export class UsecaseDB {
return source;
}

addParticipants(participant: { service: string } | { actor: string }) {
if ('actor' in participant && !this.nodes.some((node) => node.id === participant.actor)) {
this.nodes.push(new UsecaseNode(participant.actor, 'actor'));
} else if (
'service' in participant &&
!this.nodes.some((node) => node.id === participant.service)
) {
this.nodes.push(new UsecaseNode(participant.service, 'service'));
addParticipant(participant: { service: string; as?: string } | { actor: string; as?: string }) {
setLogLevel('info');
log.info('addParticipants', participant);
const nodeType = 'actor' in participant ? 'actor' : 'service';
const id = 'actor' in participant ? participant.actor.trim() : participant.service.trim();
const _ = this.getOrCreateNode(id, nodeType);
if (participant.as) {
this.addAlias(`${id} as ${participant.as}`);
}
}

Expand All @@ -69,8 +67,8 @@ export class UsecaseDB {
addRelationship(source: string, target: string, arrowString: string): void {
source = common.sanitizeText(source, getConfig());
target = common.sanitizeText(target, getConfig());
const sourceNode = this.getNode(source, isUseCaseLabel(source) ? 'usecase' : 'actor');
const targetNode = this.getNode(target, isUseCaseLabel(target) ? 'usecase' : 'service');
const sourceNode = this.getOrCreateNode(source, isUseCaseLabel(source) ? 'usecase' : 'actor');
const targetNode = this.getOrCreateNode(target, isUseCaseLabel(target) ? 'usecase' : 'service');
const label = (/--(.+?)(-->|->)/.exec(arrowString)?.[1] ?? '').trim();
const arrow = arrowString.includes('-->') ? '-->' : '->';
this.links.push(new UsecaseLink(sourceNode, targetNode, arrow, label));
Expand All @@ -87,7 +85,9 @@ export class UsecaseDB {
}

getActors() {
return this.links.map((link) => link.source.id).filter((source) => !isUseCaseLabel(source));
return [...this.nodesMap.values()]
.filter((node) => node.nodeType === 'actor')
.map((node) => node.id);
}

getConfig() {
Expand Down Expand Up @@ -125,7 +125,7 @@ export class UsecaseDB {
);

const nodes: Node[] = [
...this.nodes.map(
...[...this.nodesMap.values()].map(
(node) =>
({
...baseNode,
Expand All @@ -148,6 +148,8 @@ export class UsecaseDB {
),
];

// Remove () from use case labels
// and make them rounded.
nodes
.filter((node) => isUseCaseLabel(node.label))
.forEach((node) => {
Expand Down Expand Up @@ -176,27 +178,19 @@ export class UsecaseDB {
}

getServices(): string[] {
const services = [];
for (const node of this.nodes) {
if (node.nodeType === 'service') {
services.push(node.id);
}
}
return services;
return [...this.nodesMap.values()]
.filter((node) => node.nodeType === 'service')
.map((node) => node.id);
}

getSystemBoundaries() {
return this.systemBoundaries;
}

getUseCases(): string[] {
const useCases = [];
for (const node of this.nodes) {
if (node.nodeType === 'usecase') {
useCases.push(node.id);
}
}
return useCases;
return [...this.nodesMap.values()]
.filter((node) => node.nodeType === 'usecase')
.map((node) => node.id);
}

getAccDescription = getAccDescription;
Expand All @@ -207,11 +201,10 @@ export class UsecaseDB {
setAccDescription = setAccDescription;
setDiagramTitle = setDiagramTitle;

private getNode(id: string, nodeType: UsecaseNodeType): UsecaseNode {
private getOrCreateNode(id: string, nodeType: UsecaseNodeType): UsecaseNode {
if (!this.nodesMap.has(id)) {
const node = new UsecaseNode(id, nodeType);
this.nodesMap.set(id, node);
this.nodes.push(node);
}
return this.nodesMap.get(id)!;
}
Expand All @@ -234,6 +227,9 @@ export class UsecaseLink {
) {}
}

/**
* can be any of actor, service, or usecase
*/
export class UsecaseNode {
constructor(
public id: string,
Expand All @@ -249,7 +245,7 @@ type ArrowType = '->' | '-->';
const db = new UsecaseDB();
export default {
clear: db.clear.bind(db),
addParticipants: db.addParticipants.bind(db),
addParticipant: db.addParticipant.bind(db),
addRelationship: db.addRelationship.bind(db),
addAlias: db.addAlias.bind(db),
getAccDescription,
Expand Down

0 comments on commit a3fb490

Please sign in to comment.