fix: add imperativeState and replace sequenceDb global variables with it

This commit is contained in:
Faris Nabiev
2023-10-12 16:39:31 +03:00
parent df068dbde8
commit b4f444869e
4 changed files with 199 additions and 77 deletions

1
.npmrc
View File

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

View File

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

View File

@@ -0,0 +1,78 @@
import { createImperativeState, domain } from './imperativeState.js';
describe('domain.optional', () => {
it('should set undefined without args', () => {
expect(domain.optional()).toBeUndefined();
});
it('should set identity with args', () => {
const value = {};
expect(domain.optional(value)).toEqual(value);
});
});
describe('domain.identity', () => {
it('should set identity', () => {
const value = {};
expect(domain.identity(value)).toEqual(value);
});
});
describe('createImperativeState', () => {
it('should create state with values from initializer', () => {
const baz = {
flag: false,
};
const state = createImperativeState(() => ({
foo: domain.optional<number>(),
bar: domain.identity<string[]>([]),
baz,
}));
expect(state.records.foo).toBeUndefined();
expect(state.records.bar).toEqual([]);
expect(state.records.baz).toBe(baz);
});
it('should update records', () => {
const state = createImperativeState(() => ({
foo: domain.optional<number>(),
bar: domain.identity<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 = createImperativeState(() => ({
foo: domain.optional<number>(),
bar: domain.identity<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 @@
export const createImperativeState = <S extends Record<string, unknown>>(init: () => S) => {
const state = init();
return {
get records() {
return state;
},
reset: () => {
Object.keys(state).forEach((key) => {
delete state[key];
});
Object.entries(init()).forEach(([key, value]: [keyof S, any]) => {
state[key] = value;
});
},
};
};
export const domain = {
optional: <V>(value?: V) => value,
identity: <V>(value: V) => value,
} as const;
/*
const state = createImperativeState(() => ({
foo: domain.optional<string>(),
bar: domain.identity<number[]>([]),
baz: domain.optional(1),
}));
typeof state.records:
{
foo: string | undefined, // actual: undefined
bar: number[], // actual: []
baz: number | undefined, // actual: 1
}
*/