Merge pull request #4941 from rflban/fix/1294_exhaustive-clear-sequenceDb-variables

fix/1294_exhaustive-clear-sequenceDb-variables
This commit is contained in:
Sidharth Vinod 2024-01-11 04:48:36 +00:00 committed by GitHub
commit 54446f1ec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 80 deletions

1
.npmrc
View File

@ -1,2 +1,3 @@
registry=https://registry.npmjs.org
auto-install-peers=true
strict-peer-dependencies=false

View File

@ -10,7 +10,7 @@ interface CypressConfig {
type CypressMermaidConfig = MermaidConfig & CypressConfig;
interface CodeObject {
code: string;
code: string | string[];
mermaid: CypressMermaidConfig;
}
@ -25,7 +25,7 @@ const batchId: string =
: Cypress.env('CYPRESS_COMMIT') || Date.now().toString());
export const mermaidUrl = (
graphStr: string,
graphStr: string | string[],
options: CypressMermaidConfig,
api: boolean
): string => {
@ -82,7 +82,7 @@ export const urlSnapshotTest = (
};
export const renderGraph = (
graphStr: string,
graphStr: string | string[],
options: CypressMermaidConfig = {},
api = false
): void => {

View File

@ -930,4 +930,36 @@ context('Sequence diagram', () => {
});
});
});
context('render after error', () => {
it('should render diagram after fixing destroy participant error', () => {
cy.on('uncaught:exception', (err) => {
return false;
});
renderGraph([
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bo
Bob->>Alice: I agree`,
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you ?
Bob->>Alice: Fine, thank you. And you?
create participant Carl
Alice->>Carl: Hi Carl!
create actor D as Donald
Carl->>D: Hi!
destroy Carl
Alice-xCarl: We are too many
destroy Bob
Bob->>Alice: I agree`,
]);
});
});
});

View File

@ -131,6 +131,14 @@ sequenceDiagram
Bob->>Alice: I agree
```
#### Unfixable actor/participant creation/deletion error
If an error of the following type occurs when creating or deleting an actor/participant:
> The destroyed participant **participant-name** does not have an associated destroying message after its declaration. Please check the sequence diagram.
And fixing diagram code does not get rid of this error and rendering of all other diagrams results in the same error, then you need to update the mermaid version to (v\<MERMAID_RELEASE_VERSION>+).
### Grouping / Box
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:

View File

@ -2,57 +2,60 @@ import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import { sanitizeText } from '../common/common.js';
import {
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
getAccDescription,
getAccTitle,
getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import { ImperativeState } from '../../utils/imperativeState.js';
let prevActor = undefined;
let actors = {};
let createdActors = {};
let destroyedActors = {};
let boxes = [];
let messages = [];
const notes = [];
let sequenceNumbersEnabled = false;
let wrapEnabled;
let currentBox = undefined;
let lastCreated = undefined;
let lastDestroyed = undefined;
const state = new ImperativeState(() => ({
prevActor: undefined,
actors: {},
createdActors: {},
destroyedActors: {},
boxes: [],
messages: [],
notes: [],
sequenceNumbersEnabled: false,
wrapEnabled: undefined,
currentBox: undefined,
lastCreated: undefined,
lastDestroyed: undefined,
}));
export const addBox = function (data) {
boxes.push({
state.records.boxes.push({
name: data.text,
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
fill: data.color,
actorKeys: [],
});
currentBox = boxes.slice(-1)[0];
state.records.currentBox = state.records.boxes.slice(-1)[0];
};
export const addActor = function (id, name, description, type) {
let assignedBox = currentBox;
const old = actors[id];
let assignedBox = state.records.currentBox;
const old = state.records.actors[id];
if (old) {
// If already set and trying to set to a new one throw error
if (currentBox && old.box && currentBox !== old.box) {
if (state.records.currentBox && old.box && state.records.currentBox !== old.box) {
throw new Error(
'A same participant should only be defined in one Box: ' +
old.name +
" can't be in '" +
old.box.name +
"' and in '" +
currentBox.name +
state.records.currentBox.name +
"' at the same time."
);
}
// Don't change the box if already
assignedBox = old.box ? old.box : currentBox;
assignedBox = old.box ? old.box : state.records.currentBox;
old.box = assignedBox;
// Don't allow description nulling
@ -69,36 +72,42 @@ export const addActor = function (id, name, description, type) {
description = { text: name, wrap: null, type };
}
actors[id] = {
state.records.actors[id] = {
box: assignedBox,
name: name,
description: description.text,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
prevActor: prevActor,
prevActor: state.records.prevActor,
links: {},
properties: {},
actorCnt: null,
rectData: null,
type: type || 'participant',
};
if (prevActor && actors[prevActor]) {
actors[prevActor].nextActor = id;
if (state.records.prevActor && state.records.actors[state.records.prevActor]) {
state.records.actors[state.records.prevActor].nextActor = id;
}
if (currentBox) {
currentBox.actorKeys.push(id);
if (state.records.currentBox) {
state.records.currentBox.actorKeys.push(id);
}
prevActor = id;
state.records.prevActor = id;
};
const activationCount = (part) => {
let i;
let count = 0;
for (i = 0; i < messages.length; i++) {
if (messages[i].type === LINETYPE.ACTIVE_START && messages[i].from.actor === part) {
for (i = 0; i < state.records.messages.length; i++) {
if (
state.records.messages[i].type === LINETYPE.ACTIVE_START &&
state.records.messages[i].from.actor === part
) {
count++;
}
if (messages[i].type === LINETYPE.ACTIVE_END && messages[i].from.actor === part) {
if (
state.records.messages[i].type === LINETYPE.ACTIVE_END &&
state.records.messages[i].from.actor === part
) {
count--;
}
}
@ -106,7 +115,7 @@ const activationCount = (part) => {
};
export const addMessage = function (idFrom, idTo, message, answer) {
messages.push({
state.records.messages.push({
from: idFrom,
to: idTo,
message: message.text,
@ -137,7 +146,7 @@ export const addSignal = function (
throw error;
}
}
messages.push({
state.records.messages.push({
from: idFrom,
to: idTo,
message: message.text,
@ -149,63 +158,58 @@ export const addSignal = function (
};
export const hasAtLeastOneBox = function () {
return boxes.length > 0;
return state.records.boxes.length > 0;
};
export const hasAtLeastOneBoxWithTitle = function () {
return boxes.some((b) => b.name);
return state.records.boxes.some((b) => b.name);
};
export const getMessages = function () {
return messages;
return state.records.messages;
};
export const getBoxes = function () {
return boxes;
return state.records.boxes;
};
export const getActors = function () {
return actors;
return state.records.actors;
};
export const getCreatedActors = function () {
return createdActors;
return state.records.createdActors;
};
export const getDestroyedActors = function () {
return destroyedActors;
return state.records.destroyedActors;
};
export const getActor = function (id) {
return actors[id];
return state.records.actors[id];
};
export const getActorKeys = function () {
return Object.keys(actors);
return Object.keys(state.records.actors);
};
export const enableSequenceNumbers = function () {
sequenceNumbersEnabled = true;
state.records.sequenceNumbersEnabled = true;
};
export const disableSequenceNumbers = function () {
sequenceNumbersEnabled = false;
state.records.sequenceNumbersEnabled = false;
};
export const showSequenceNumbers = () => sequenceNumbersEnabled;
export const showSequenceNumbers = () => state.records.sequenceNumbersEnabled;
export const setWrap = function (wrapSetting) {
wrapEnabled = wrapSetting;
state.records.wrapEnabled = wrapSetting;
};
export const autoWrap = () => {
// if setWrap has been called, use that value, otherwise use the value from the config
// TODO: refactor, always use the config value let setWrap update the config value
if (wrapEnabled !== undefined) {
return wrapEnabled;
if (state.records.wrapEnabled !== undefined) {
return state.records.wrapEnabled;
}
return getConfig().sequence.wrap;
};
export const clear = function () {
actors = {};
createdActors = {};
destroyedActors = {};
boxes = [];
messages = [];
sequenceNumbersEnabled = false;
state.reset();
commonClear();
};
@ -247,7 +251,7 @@ export const parseBoxData = function (str) {
}
}
const boxData = {
return {
color: color,
text:
title !== undefined
@ -262,7 +266,6 @@ export const parseBoxData = function (str) {
: undefined
: undefined,
};
return boxData;
};
export const LINETYPE = {
@ -321,8 +324,8 @@ export const addNote = function (actor, placement, message) {
// eslint-disable-next-line unicorn/prefer-spread
const actors = [].concat(actor, actor);
notes.push(note);
messages.push({
state.records.notes.push(note);
state.records.messages.push({
from: actors[0],
to: actors[1],
message: message.text,
@ -414,7 +417,7 @@ function insertProperties(actor, properties) {
*
*/
function boxEnd() {
currentBox = undefined;
state.records.currentBox = undefined;
}
export const addDetails = function (actorId, text) {
@ -468,7 +471,7 @@ export const apply = function (param) {
} else {
switch (param.type) {
case 'sequenceIndex':
messages.push({
state.records.messages.push({
from: undefined,
to: undefined,
message: {
@ -484,18 +487,18 @@ export const apply = function (param) {
addActor(param.actor, param.actor, param.description, param.draw);
break;
case 'createParticipant':
if (actors[param.actor]) {
if (state.records.actors[param.actor]) {
throw new Error(
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
);
}
lastCreated = param.actor;
state.records.lastCreated = param.actor;
addActor(param.actor, param.actor, param.description, param.draw);
createdActors[param.actor] = messages.length;
state.records.createdActors[param.actor] = state.records.messages.length;
break;
case 'destroyParticipant':
lastDestroyed = param.actor;
destroyedActors[param.actor] = messages.length;
state.records.lastDestroyed = param.actor;
state.records.destroyedActors[param.actor] = state.records.messages.length;
break;
case 'activeStart':
addSignal(param.actor, undefined, undefined, param.signalType);
@ -519,25 +522,28 @@ export const apply = function (param) {
addDetails(param.actor, param.text);
break;
case 'addMessage':
if (lastCreated) {
if (param.to !== lastCreated) {
if (state.records.lastCreated) {
if (param.to !== state.records.lastCreated) {
throw new Error(
'The created participant ' +
lastCreated +
state.records.lastCreated +
' does not have an associated creating message after its declaration. Please check the sequence diagram.'
);
} else {
lastCreated = undefined;
state.records.lastCreated = undefined;
}
} else if (lastDestroyed) {
if (param.to !== lastDestroyed && param.from !== lastDestroyed) {
} else if (state.records.lastDestroyed) {
if (
param.to !== state.records.lastDestroyed &&
param.from !== state.records.lastDestroyed
) {
throw new Error(
'The destroyed participant ' +
lastDestroyed +
state.records.lastDestroyed +
' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
);
} else {
lastDestroyed = undefined;
state.records.lastDestroyed = undefined;
}
}
addSignal(param.from, param.to, param.msg, param.signalType, param.activate);

View File

@ -83,6 +83,14 @@ sequenceDiagram
Bob->>Alice: I agree
```
#### Unfixable actor/participant creation/deletion error
If an error of the following type occurs when creating or deleting an actor/participant:
> The destroyed participant **participant-name** does not have an associated destroying message after its declaration. Please check the sequence diagram.
And fixing diagram code does not get rid of this error and rendering of all other diagrams results in the same error, then you need to update the mermaid version to (v<MERMAID_RELEASE_VERSION>+).
### Grouping / Box
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:

View File

@ -0,0 +1,60 @@
import { ImperativeState } from './imperativeState.js';
describe('createImperativeState', () => {
it('should create state with values from initializer', () => {
const baz = {
flag: false,
};
const state = new ImperativeState(() => ({
foo: undefined as number | undefined,
bar: [] as string[],
baz,
}));
expect(state.records.foo).toBeUndefined();
expect(state.records.bar).toEqual([]);
expect(state.records.baz).toBe(baz);
});
it('should update records', () => {
const state = new ImperativeState(() => ({
foo: undefined as number | undefined,
bar: [] as string[],
baz: {
flag: false,
},
}));
state.records.foo = 5;
state.records.bar = ['hello'];
state.records.baz.flag = true;
expect(state.records.foo).toEqual(5);
expect(state.records.bar).toEqual(['hello']);
expect(state.records.baz).toEqual({
flag: true,
});
});
it('should reset records', () => {
const state = new ImperativeState(() => ({
foo: undefined as number | undefined,
bar: [] as string[],
baz: {
flag: false,
},
}));
state.records.foo = 5;
state.records.bar = ['hello'];
state.records.baz.flag = true;
state.reset();
expect(state.records.foo).toBeUndefined();
expect(state.records.bar).toEqual([]);
expect(state.records.baz).toEqual({
flag: false,
});
});
});

View File

@ -0,0 +1,37 @@
/**
* Resettable state storage.
* @example
* ```
* const state = new ImperativeState(() => {
* foo: undefined as string | undefined,
* bar: [] as number[],
* baz: 1 as number | undefined,
* });
*
* state.records.foo = "hi";
* console.log(state.records.foo); // prints "hi";
* state.reset();
* console.log(state.records.foo); // prints "default";
*
* // typeof state.records:
* // {
* // foo: string | undefined, // actual: undefined
* // bar: number[], // actual: []
* // baz: number | undefined, // actual: 1
* // }
* ```
*/
export class ImperativeState<S extends Record<string, unknown>> {
public records: S;
/**
* @param init - Function that creates the default state.
*/
constructor(private init: () => S) {
this.records = this.init();
}
reset() {
this.records = this.init();
}
}