Skip to content

Commit

Permalink
Add test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
teyc committed Dec 20, 2024
1 parent 1af5c0d commit 041d008
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ describe('diagram-orchestration', () => {
{ text: 'gitGraph', expected: 'gitGraph' },
{ text: 'stateDiagram', expected: 'state' },
{ text: 'stateDiagram-v2', expected: 'stateDiagram' },
{ text: 'usecase-beta', expected: 'usecase' },
])(
'should $text be detected as $expected',
({ text, expected }: { text: string; expected: string }) => {
Expand Down
31 changes: 22 additions & 9 deletions packages/mermaid/src/diagrams/usecase/parser/usecase.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// @ts-ignore: jison doesn't export types
import usecase from './usecase.jison';
import db, { UsecaseLink, UsecaseNode } from '../usecaseDB.js';
import { cleanupComments } from '../../../diagram-api/comments.js';
import db, { type UsecaseDB, UsecaseLink, UsecaseNode } from '../usecaseDB.js';
import { prepareTextForParsing } from '../usecaseUtils.js';
import * as fs from 'fs';
import * as path from 'path';
import { setConfig } from '../../../config.js';

setConfig({
securityLevel: 'strict',
});

describe('Usecase diagram', function () {
describe('when parsing a use case it', function () {
Expand All @@ -28,16 +30,21 @@ describe('Usecase diagram', function () {

const relationships = usecase.yy.getRelationships();
expect(relationships).toStrictEqual([
new UsecaseLink(new UsecaseNode('User'), new UsecaseNode('(Start)'), '->', ''),
new UsecaseLink(
new UsecaseNode('User'),
new UsecaseNode('(Use the application)'),
new UsecaseNode('User', 'actor'),
new UsecaseNode('(Start)', 'usecase'),
'->',
''
),
new UsecaseLink(
new UsecaseNode('User', 'actor'),
new UsecaseNode('(Use the application)', 'usecase'),
'-->',
''
),
new UsecaseLink(
new UsecaseNode('(Use the application)'),
new UsecaseNode('(Another use case)'),
new UsecaseNode('(Use the application)', 'usecase'),
new UsecaseNode('(Another use case)', 'usecase'),
'-->',
''
),
Expand Down Expand Up @@ -119,6 +126,12 @@ describe('Usecase diagram', function () {
const result = usecase.parse(prepareTextForParsing(input));
expect(result).toBeTruthy();
expect(usecase.yy.getDiagramTitle()).toEqual('Arrows in Use Case diagrams');

const db = usecase.yy as UsecaseDB;
expect(
db.getRelationships().some((rel) => rel.source.id == 'Admin' && rel.target.id == '(Login)')
).toBeTruthy();

// console.log(usecase.yy.getRelationships());
// console.log(usecase.yy.getSystemBoundaries());
});
Expand Down
41 changes: 32 additions & 9 deletions packages/mermaid/src/diagrams/usecase/usecaseDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import {
clear as commonClear,
} from '../common/commonDb.js';

function isUseCaseLabel(label: string | undefined) {
if (!label) {
return false;
}
label = label.trim();
return label.startsWith('(') && label.endsWith(')');
}

export class UsecaseDB {
private aliases = new Map<string, string>();
private links: UsecaseLink[] = [];
Expand Down Expand Up @@ -41,20 +49,20 @@ export class UsecaseDB {

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));
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));
this.nodes.push(new UsecaseNode(participant.service, 'service'));
}
}

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

getActors() {
return this.links.map((link) => link.source.id).filter((source) => !source.startsWith('('));
return this.links.map((link) => link.source.id).filter((source) => !isUseCaseLabel(source));
}

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

nodes
.filter((node) => node.label?.startsWith('(') && node.label.endsWith(')'))
.filter((node) => isUseCaseLabel(node.label))
.forEach((node) => {
node.label = node.label!.slice(1, -1);
node.rx = 50;
Expand All @@ -159,6 +167,16 @@ export class UsecaseDB {
return this.links;
}

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

getSystemBoundaries() {
return this.systemBoundaries;
}
Expand All @@ -171,9 +189,9 @@ export class UsecaseDB {
setAccDescription = setAccDescription;
setDiagramTitle = setDiagramTitle;

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

export class UsecaseNode {
constructor(public id: string) {}
constructor(
public id: string,
public nodeType: UsecaseNodeType | undefined
) {}
}

type UsecaseNodeType = 'actor' | 'service' | 'usecase';

type ArrowType = '->' | '-->';

// Export an instance of the class
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest';
import { shapes, isValidShape, type ShapeID, shapesDefs } from './shapes.js';

describe('shapes', () => {
it('should have a valid shape handler for each shape', () => {
Object.keys(shapes).forEach((shape) => {
expect(typeof shapes[shape as ShapeID]).toBe('function');
});
});

it('should return true for valid shape IDs', () => {
const validShapes: ShapeID[] = Object.keys(shapes) as ShapeID[];
validShapes.forEach((shape) => {
expect(isValidShape(shape)).toBe(true);
});
});

it('should return false for invalid shape IDs', () => {
const invalidShapes = ['invalidShape1', 'invalidShape2', 'invalidShape3'];
invalidShapes.forEach((shape) => {
expect(isValidShape(shape)).toBe(false);
});
});

/*
it('should have unique short names and aliases', () => {
const allNames = new Set<string>();
shapesDefs.forEach((shape) => {
const names = [shape.shortName, ...(shape.aliases ?? []), ...(shape.internalAliases ?? [])];
names.forEach((name) => {
expect(allNames.has(name)).toBe(false);
allNames.add(name);
});
});
});
*/

it('should have a handler for each shape definition', () => {
shapesDefs.forEach((shape) => {
expect(typeof shape.handler).toBe('function');
});
});

it('should have a semantic name, name, short name, and description for each shape definition', () => {
shapesDefs.forEach((shape) => {
expect(typeof shape.semanticName).toBe('string');
expect(typeof shape.name).toBe('string');
expect(typeof shape.shortName).toBe('string');
expect(typeof shape.description).toBe('string');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export interface ShapeDefinition {
handler: ShapeHandler;
}

export const shapesDefs = [
export const shapesDefs: ShapeDefinition[] = [
{
semanticName: 'Actor',
name: 'Actor',
Expand Down

0 comments on commit 041d008

Please sign in to comment.