Compare commits

..

57 Commits

Author SHA1 Message Date
Sidharth Vinod
0518e9cfd7 Merge pull request #5984 from mermaid-js/renovate/eslint
chore(deps): update eslint (minor)
2025-02-21 10:26:12 +00:00
autofix-ci[bot]
f43e35039f [autofix.ci] apply automated fixes 2025-02-20 18:31:20 +00:00
renovate[bot]
dbf885b077 chore(deps): update eslint 2025-02-20 18:25:55 +00:00
Sidharth Vinod
ad2f17205a Merge pull request #6272 from mermaid-js/saurabh/refactor/convert-sequenceDb-to-class
Refactor: Change SequenceDB to class based architecture.
2025-02-20 07:20:14 +00:00
Sidharth Vinod
98fe7a6e03 Merge pull request #5997 from mermaid-js/renovate/major-eslint
chore(deps): update eslint (major)
2025-02-19 12:48:14 +05:30
saurabhg772244
5857953d51 Merge branch 'develop' of https://github.com/mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-19 11:26:22 +05:30
Sidharth Vinod
34a12103d0 Merge branch 'renovate/major-eslint' of https://github.com/mermaid-js/mermaid into renovate/major-eslint
* 'renovate/major-eslint' of https://github.com/mermaid-js/mermaid:
  chore(deps): update eslint
2025-02-19 00:34:03 +05:30
Sidharth Vinod
46b8a75199 chore: Remove deprecated rule 2025-02-19 00:33:56 +05:30
renovate[bot]
1dca45a76d chore(deps): update eslint 2025-02-18 19:01:39 +00:00
Sidharth Vinod
d1d5bd14f6 Merge branch 'develop' into renovate/major-eslint
* develop:
  chore: Add tslib
  [autofix.ci] apply automated fixes
  fix(deps): update all patch dependencies
2025-02-19 00:28:32 +05:30
Sidharth Vinod
974867a089 Merge pull request #5961 from mermaid-js/renovate/patch-all-patch
fix(deps): update all patch dependencies (patch)
2025-02-19 00:24:41 +05:30
Sidharth Vinod
52532a7801 Merge pull request #6295 from mermaid-js/omkar/fix-state-diagram-default-direction
Fix: state diagram default direction update
2025-02-18 18:53:57 +00:00
Sidharth Vinod
6eb56b9363 Merge branch 'develop' into renovate/patch-all-patch
* develop: (97 commits)
  chore: Update actions/cache
  chore: Update actions/cache
  fix: CodeQL double escape warning
  chore(deps): update eslint
  Added changeset.
  refactor: handle StateDB `.extract()` properly
  chore(deps): update dependency esbuild to ^0.25.0 [security]
  Update .changeset/grumpy-cheetahs-deliver.md
  chore: Use RUN_VISUAL_TEST in cypress config
  chore: Use argos tokenless auth
  chore: Add tokenless auth to argos
  fix(deps): update dependency dompurify to v3.2.4 [security]
  chore: Update argos token
  Ignore huehive
  chore: Run argos on all PRs, except renovate bot's
  chore: Fix exclude
  [autofix.ci] apply automated fixes
  chore(deps): update dependency vitest to v1.6.1 [security]
  docs: Cleanup minor issues
  Add files via upload
  ...
2025-02-19 00:08:50 +05:30
Sidharth Vinod
f7c91a72cb chore: Add tslib 2025-02-19 00:06:45 +05:30
renovate[bot]
c53e044559 chore(deps): update eslint 2025-02-18 18:26:30 +00:00
Sidharth Vinod
d846a8c65a chore: Minor refactor 2025-02-18 23:48:19 +05:30
Sidharth Vinod
c3ba23a4e9 Merge branch 'develop' into omkar/fix-state-diagram-default-direction 2025-02-18 23:44:19 +05:30
Sidharth Vinod
c688c387a0 Merge pull request #6298 from mermaid-js/saurabh/fix/sanitizeText-codeql-warning
fix: CodeQL double escape warning
2025-02-18 23:41:31 +05:30
Sidharth Vinod
300fb85a73 chore: Update actions/cache 2025-02-18 23:41:01 +05:30
Sidharth Vinod
9c6e554153 chore: Update actions/cache 2025-02-18 23:40:53 +05:30
Sidharth Vinod
b5c78b7225 Merge pull request #5908 from mermaid-js/renovate/patch-eslint
chore(deps): update eslint (patch)
2025-02-18 21:42:23 +05:30
omkarht
da6361f652 added changeset 2025-02-18 18:36:52 +05:30
omkarht
167fef5cff added test cases to check state diagram direction 2025-02-18 17:23:48 +05:30
saurabhg772244
3233c63fc0 Updated variable name 2025-02-18 16:59:35 +05:30
saurabhg772244
1182aaaf4d fix: CodeQL double escape warning 2025-02-18 15:42:25 +05:30
omkarht
b78c061a8f refatored code 2025-02-18 15:40:45 +05:30
renovate[bot]
ebfcf3ea07 chore(deps): update eslint 2025-02-17 15:45:31 +00:00
Sidharth Vinod
62edea6b33 Merge pull request #6293 from mermaid-js/saurabh/refactor/handle-StateDB-.extract-properly
Handle StateDB `.extract()` properly
2025-02-17 21:13:26 +05:30
omkarht
72eda9ce52 Fix: state diagram default direction update 2025-02-17 20:53:31 +05:30
saurabhg772244
cfd84e54d5 Added changeset. 2025-02-17 16:14:55 +05:30
Alois Klink
cfe710f42b refactor: handle StateDB .extract() properly
Fix the StateDB properly for stateDiagram-v1 vs stateDiagram-v2.
2025-02-17 15:54:19 +05:30
saurabhg772244
fe6f05eec9 Merge branch 'develop' of github.com-mermaid:mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-15 22:56:53 +05:30
Sidharth Vinod
548256507d Merge pull request #6269 from mermaid-js/renovate/npm-esbuild-vulnerability
chore(deps): update dependency esbuild to ^0.25.0 [security]
2025-02-15 16:51:26 +00:00
Sidharth Vinod
d8ebf7a136 Merge pull request #6250 from mermaid-js/saurabh/refactor/convert-stateDb-to-class-v2
Refactor: V2. Change StateDB to class based architecture.
2025-02-15 16:50:55 +00:00
renovate[bot]
a758b24b75 chore(deps): update dependency esbuild to ^0.25.0 [security] 2025-02-15 16:16:28 +00:00
Saurabh Gore
88abf46fc3 Update .changeset/grumpy-cheetahs-deliver.md
Co-authored-by: Sidharth Vinod <github@sidharth.dev>
2025-02-15 21:33:58 +05:30
Sidharth Vinod
2eeba0e999 Merge branch 'develop' into saurabh/refactor/convert-stateDb-to-class-v2 2025-02-15 21:31:14 +05:30
Sidharth Vinod
397684fe49 Merge pull request #6284 from mermaid-js/renovate/npm-dompurify-vulnerability
fix(deps): update dependency dompurify to v3.2.4 [security]
2025-02-15 16:00:56 +00:00
Sidharth Vinod
bc1f769f13 Merge pull request #6287 from sidharthv96/sidv/tokenlessArgos
chore: Add tokenless auth to argos
2025-02-15 21:30:39 +05:30
Sidharth Vinod
f6592235a2 chore: Use RUN_VISUAL_TEST in cypress config 2025-02-15 21:05:47 +05:30
Sidharth Vinod
57805dafa4 chore: Use argos tokenless auth 2025-02-15 20:51:56 +05:30
renovate[bot]
7a7e4b4e6f fix(deps): update dependency dompurify to v3.2.4 [security] 2025-02-15 14:39:21 +00:00
Sidharth Vinod
62f9261c4e Merge branch 'develop' into saurabh/refactor/convert-stateDb-to-class-v2 2025-02-14 21:21:00 +05:30
saurabhg772244
451c886f50 Code refactor. 2025-02-14 19:17:09 +05:30
saurabhg772244
0a1a112605 Move setWrap below clear in constructor. 2025-02-14 17:51:17 +05:30
saurabhg772244
6ca8f46d37 Updated unit tests. 2025-02-12 16:15:06 +05:30
saurabhg772244
b2dfbb6ffd Merge branch 'develop' of https://github.com/mermaid-js/mermaid into saurabh/refactor/convert-sequenceDb-to-class 2025-02-12 16:04:54 +05:30
saurabhg772244
ffa7804af0 added changeset 2025-02-12 16:04:22 +05:30
saurabhg772244
f454865b97 Updated unit tests to reflect new DB instance per parse. 2025-02-12 16:00:14 +05:30
saurabhg772244
d83b1a5dbc Refactored arrow function to regular function and assigned unique ID to messages during parsing. 2025-02-12 15:54:51 +05:30
saurabhg772244
4dbaa2b5d6 code refactor 2025-02-04 19:18:46 +05:30
saurabhg772244
9cad3c7aea added changeset 2025-02-04 17:09:09 +05:30
saurabhg772244
56d66cdabc Fix issue where data was not being set in the db after parsing. 2025-02-04 16:45:45 +05:30
saurabhg772244
a0e5408850 convert stateDb to class, added test case. 2025-02-04 16:41:54 +05:30
saurabhg772244
5d3d1047a4 convert sequenceDb to class. 2025-01-15 15:37:33 +05:30
autofix-ci[bot]
59352ad4d8 [autofix.ci] apply automated fixes 2025-01-11 16:39:40 +00:00
renovate[bot]
ba70f15de5 fix(deps): update all patch dependencies 2025-01-11 16:32:14 +00:00
36 changed files with 3205 additions and 2333 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
`mermaidAPI.getDiagramFromText()` now returns a new db instance on each call for state diagrams

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
Added versioning to StateDB and updated tests and diagrams to use it.

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `mermaidAPI.getDiagramFromText()` now returns a new different db for each sequence diagram. Added unique IDs for messages.

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `getDirection` and `setDirection` in `stateDb` refactored to return and set actual direction

View File

@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
with:
config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
@@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -62,4 +62,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13

View File

@@ -17,4 +17,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: 'Dependency Review'
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5

View File

@@ -26,11 +26,11 @@ jobs:
with:
node-version-file: '.node-version'
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
id: cypress
with:
install: false

View File

@@ -45,9 +45,8 @@ jobs:
node-version-file: '.node-version'
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
save-always: true
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
@@ -60,7 +59,7 @@ jobs:
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with:
# just perform install
runTests: false
@@ -96,13 +95,13 @@ jobs:
# These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots
id: cache-snapshot
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache/restore@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
with:
runTests: false
@@ -118,7 +117,7 @@ jobs:
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
uses: cypress-io/github-action@57b70560982e6a11d23d4b8bec7f8a487cdbb71b # v6.7.8
id: cypress
with:
install: false
@@ -129,14 +128,9 @@ jobs:
# e.g. if this action was run from a fork
record: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY != '' }}
env:
# Only set Argos environment variables if the visual test comment trigger is present
ARGOS_TOKEN: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.ARGOS_TOKEN || '' }}
ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }}
ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }}
ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_JOB: ${{ github.job }}
GITHUB_RUN_ID: ${{ github.run_id }}
CYPRESS_COMMIT: ${{ github.sha }}
CYPRESS_RECORD_KEY: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY || ''}}
SPLIT: ${{ strategy.job-total }}

View File

@@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Restore lychee cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
uses: actions/cache@0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.1
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}

View File

@@ -36,7 +36,7 @@ jobs:
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8
uses: changesets/action@c8bada60c408975afd1a20b3db81d6eee6789308 # v1.4.9
with:
version: pnpm changeset:version
publish: pnpm changeset:publish

View File

@@ -32,6 +32,6 @@ jobs:
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
with:
sarif_file: results.sarif

View File

@@ -5,14 +5,6 @@ import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import cypressSplit from 'cypress-split';
const encodeArgosToken = (options: {
owner: string;
repository: string;
jobId: string;
runId: string;
}) => `tokenless-github-${Buffer.from(JSON.stringify(options), 'utf8').toString('base64')}`;
// cspell:ignore tokenless
export default eyesPlugin(
defineConfig({
projectId: 'n2sma2',
@@ -31,26 +23,10 @@ export default eyesPlugin(
});
// copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.CI && !!process.env.ARGOS_TOKEN;
config.env.useArgos = process.env.RUN_VISUAL_TEST === 'true';
if (config.env.useArgos) {
if (!process.env.GITHUB_REPOSITORY) {
throw new Error('GITHUB_REPOSITORY is not set');
}
if (!process.env.GITHUB_JOB) {
throw new Error('GITHUB_JOB is not set');
}
if (!process.env.GITHUB_RUN_ID) {
throw new Error('GITHUB_RUN_ID is not set');
}
registerArgosTask(on, config, {
token: encodeArgosToken({
owner: process.env.GITHUB_REPOSITORY.split('/')[0],
repository: process.env.GITHUB_REPOSITORY.split('/')[1],
jobId: process.env.GITHUB_JOB,
runId: process.env.GITHUB_RUN_ID,
}),
});
registerArgosTask(on, config);
} else {
addMatchImageSnapshotPlugin(on, config);
}

View File

@@ -11,7 +11,7 @@
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"
rel="stylesheet"
/>
<link

View File

@@ -137,7 +137,6 @@ export default tseslint.config(
'unicorn/no-instanceof-array': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unsafe-regex': 'warn',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error',

View File

@@ -92,10 +92,10 @@
"cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.0",
"esbuild": "^0.21.5",
"esbuild": "^0.25.0",
"eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-cypress": "^3.3.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-cypress": "^4.0.0",
"eslint-plugin-html": "^8.1.1",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-jsdoc": "^50.0.0",
@@ -103,8 +103,8 @@
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^57.0.0",
"express": "^4.19.1",
"globals": "^15.4.0",
"globby": "^14.0.1",
@@ -123,6 +123,7 @@
"rimraf": "^5.0.5",
"rollup-plugin-visualizer": "^5.12.0",
"start-server-and-test": "^2.0.3",
"tslib": "^2.8.1",
"tsx": "^4.7.1",
"typescript": "~5.4.5",
"typescript-eslint": "^8.0.0-alpha.34",

View File

@@ -230,7 +230,7 @@ const ConfigWarning = {
} as const;
type ConfigWarningStrings = keyof typeof ConfigWarning;
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {};
const issuedWarnings: Partial<Record<ConfigWarningStrings, boolean>> = {};
const issueWarning = (warning: ConfigWarningStrings) => {
if (issuedWarnings[warning]) {
return;

View File

@@ -106,9 +106,7 @@ export const isValidArchitectureDirectionPair = function (
return x !== 'LL' && x !== 'RR' && x !== 'TT' && x !== 'BB';
};
export type ArchitectureDirectionPairMap = {
[key in ArchitectureDirectionPair]?: string;
};
export type ArchitectureDirectionPairMap = Partial<Record<ArchitectureDirectionPair, string>>;
/**
* Creates a pair of the directions of each side of an edge. This function should be used instead of manually creating it to ensure that the source is always the first character.

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,26 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types
import parser from './parser/sequenceDiagram.jison';
import db from './sequenceDb.js';
import { SequenceDB } from './sequenceDb.js';
import styles from './styles.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
import renderer from './sequenceRenderer.js';
import type { MermaidConfig } from '../../config.type.js';
export const diagram: DiagramDefinition = {
parser,
db,
get db() {
return new SequenceDB();
},
renderer,
styles,
init: ({ wrap }) => {
db.setWrap(wrap);
init: (cnf: MermaidConfig) => {
if (!cnf.sequence) {
cnf.sequence = {};
}
if (cnf.wrap) {
cnf.sequence.wrap = cnf.wrap;
setConfig({ sequence: { wrap: cnf.wrap } });
}
},
};

View File

@@ -1538,7 +1538,6 @@ const calculateLoopBounds = async function (messages, actors, _maxWidthPerActor,
let current, noteModel, msgModel;
for (const msg of messages) {
msg.id = utils.random({ length: 10 });
switch (msg.type) {
case diagObj.db.LINETYPE.LOOP_START:
case diagObj.db.LINETYPE.ALT_START:

View File

@@ -20,6 +20,7 @@ export interface Actor {
}
export interface Message {
id: string;
from?: string;
to?: string;
message:

View File

@@ -1,4 +1,4 @@
import stateDb from '../stateDb.js';
import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js';
@@ -7,7 +7,9 @@ setConfig({
});
describe('state parser can parse...', () => {
let stateDb;
beforeEach(function () {
stateDb = new StateDB(2);
stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear();
});
@@ -18,7 +20,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2
state "Small State 1" as namedState1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined();
@@ -31,7 +32,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2
namedState1 : Small State 1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined();
@@ -42,7 +42,6 @@ describe('state parser can parse...', () => {
const diagramText = `stateDiagram-v2
namedState1:Small State 1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined();
@@ -60,7 +59,6 @@ describe('state parser can parse...', () => {
state assemblies
`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('assemble')).not.toBeUndefined();
expect(states.get('assemblies')).not.toBeUndefined();
@@ -71,7 +69,6 @@ describe('state parser can parse...', () => {
state "as" as as
`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('as')).not.toBeUndefined();
expect(states.get('as').descriptions.join(' ')).toEqual('as');
@@ -96,7 +93,6 @@ describe('state parser can parse...', () => {
namedState2 --> bigState2: should point to \\nbigState2 container`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('namedState1')).not.toBeUndefined();
@@ -120,7 +116,6 @@ describe('state parser can parse...', () => {
inner1 --> inner2
}`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get('bigState1')).not.toBeUndefined();
@@ -137,7 +132,6 @@ describe('state parser can parse...', () => {
stateDiagram-v2
[*] --> ${prop}
${prop} --> [*]`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states.get(prop)).not.toBeUndefined();
});

View File

@@ -1,13 +1,15 @@
import stateDb from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
import { setConfig } from '../../../config.js';
import { StateDB } from '../stateDb.js';
import stateDiagram from './stateDiagram.jison';
setConfig({
securityLevel: 'strict',
});
describe('ClassDefs and classes when parsing a State diagram', () => {
let stateDb;
beforeEach(function () {
stateDb = new StateDB(2);
stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear();
});
@@ -16,7 +18,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
describe('defining (classDef)', () => {
it('has "classDef" as a keyword, an id, and can set a css style attribute', function () {
stateDiagram.parser.parse('stateDiagram-v2\n classDef exampleClass background:#bbb;');
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(1);
@@ -27,7 +28,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleClass background:#bbb, font-weight:bold, font-style:italic;'
);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const styleClasses = stateDb.getClasses();
expect(styleClasses.get('exampleClass').styles.length).toEqual(3);
@@ -41,7 +41,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background:#bbb,border:1.5px solid red;'
);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -53,7 +52,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(
'stateDiagram-v2\n classDef exampleStyleClass background: #bbb,border:1.5px solid red;'
);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -65,7 +63,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(
'stateDiagram-v2\n classDef __proto__ background:#bbb,border:1.5px solid red;\n classDef constructor background:#bbb,border:1.5px solid red;'
);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('__proto__').styles.length).toBe(2);
expect(classes.get('constructor').styles.length).toBe(2);
@@ -81,7 +78,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a exampleStyleClass';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDb.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toEqual(2);
@@ -102,7 +98,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a_a exampleStyleClass';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses();
expect(classes.get('exampleStyleClass').styles.length).toBe(2);
@@ -122,7 +117,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'a --> b:::exampleStyleClass' + '\n';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
const classes = stateDiagram.parser.yy.getClasses();
@@ -141,7 +135,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += '[*]:::exampleStyleClass --> b\n';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
const classes = stateDiagram.parser.yy.getClasses();
@@ -161,7 +154,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b exampleStyleClass';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
let classes = stateDiagram.parser.yy.getClasses();
let states = stateDiagram.parser.yy.getStates();
@@ -180,7 +172,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += 'class a,b,c, d, e exampleStyleClass';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const classes = stateDiagram.parser.yy.getClasses();
const states = stateDiagram.parser.yy.getStates();
@@ -208,7 +199,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
diagram += '}\n';
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
@@ -224,7 +214,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
stateDiagram.parser.parse(`stateDiagram-v2
id1
style id1 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -234,7 +223,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id1
id2
style id1,id2 background:#bbb`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
@@ -247,7 +235,6 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
id2
style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const data4Layout = stateDiagram.parser.yy.getData();
expect(data4Layout.nodes[0].cssStyles).toEqual([

View File

@@ -1,6 +1,6 @@
import { line, curveBasis } from 'd3';
import idCache from './id-cache.js';
import stateDb from './stateDb.js';
import { StateDB } from './stateDb.js';
import utils from '../../utils.js';
import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
@@ -414,13 +414,13 @@ let edgeCount = 0;
export const drawEdge = function (elem, path, relation) {
const getRelationType = function (type) {
switch (type) {
case stateDb.relationType.AGGREGATION:
case StateDB.relationType.AGGREGATION:
return 'aggregation';
case stateDb.relationType.EXTENSION:
case StateDB.relationType.EXTENSION:
return 'extension';
case stateDb.relationType.COMPOSITION:
case StateDB.relationType.COMPOSITION:
return 'composition';
case stateDb.relationType.DEPENDENCY:
case StateDB.relationType.DEPENDENCY:
return 'dependency';
}
};
@@ -459,7 +459,7 @@ export const drawEdge = function (elem, path, relation) {
svgPath.attr(
'marker-end',
'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')'
'url(' + url + '#' + getRelationType(StateDB.relationType.DEPENDENCY) + 'End' + ')'
);
if (relation.title !== undefined) {

View File

@@ -3,11 +3,14 @@
*/
// default diagram direction
export const DEFAULT_DIAGRAM_DIRECTION = 'LR';
export const DEFAULT_DIAGRAM_DIRECTION = 'TB';
// default direction for any nested documents (composites)
export const DEFAULT_NESTED_DOC_DIR = 'TB';
// parsed statement type for a direction
export const STMT_DIRECTION = 'dir';
// parsed statement type for a state
export const STMT_STATE = 'state';
// parsed statement type for a relation

View File

@@ -1,28 +1,29 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import { log } from '../../logger.js';
import { generateId } from '../../utils.js';
import common from '../common/common.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
setDiagramTitle,
getAccDescription,
getAccTitle,
getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle,
} from '../common/commonDb.js';
import { dataFetcher, reset as resetDataFetching } from './dataFetcher.js';
import { getDir } from './stateRenderer-v3-unified.js';
import {
DEFAULT_DIAGRAM_DIRECTION,
STMT_STATE,
STMT_RELATION,
STMT_CLASSDEF,
STMT_STYLEDEF,
STMT_APPLYCLASS,
DEFAULT_STATE_TYPE,
DIVIDER_TYPE,
STMT_APPLYCLASS,
STMT_CLASSDEF,
STMT_DIRECTION,
STMT_RELATION,
STMT_STATE,
STMT_STYLEDEF,
} from './stateCommon.js';
const START_NODE = '[*]';
@@ -46,15 +47,6 @@ function newClassesList() {
return new Map();
}
let nodes = [];
let edges = [];
let direction = DEFAULT_DIAGRAM_DIRECTION;
let rootDoc = [];
let classes = newClassesList(); // style classes defined by a classDef
// --------------------------------------
const newDoc = () => {
return {
/** @type {{ id1: string, id2: string, relationTitle: string }[]} */
@@ -63,40 +55,109 @@ const newDoc = () => {
documents: {},
};
};
let documents = {
const clone = (o) => JSON.parse(JSON.stringify(o));
export class StateDB {
/**
* @param {1 | 2} version - v1 renderer or v2 renderer.
*/
constructor(version) {
this.clear();
this.version = version;
// Needed for JISON since it only supports direct properties
this.setRootDoc = this.setRootDoc.bind(this);
this.getDividerId = this.getDividerId.bind(this);
this.setDirection = this.setDirection.bind(this);
this.trimColon = this.trimColon.bind(this);
}
/**
* @private
* @type {1 | 2}
*/
version;
/**
* @private
* @type {Array}
*/
nodes = [];
/**
* @private
* @type {Array}
*/
edges = [];
/**
* @private
* @type {Array}
*/
rootDoc = [];
/**
* @private
* @type {Map<string, any>}
*/
classes = newClassesList(); // style classes defined by a classDef
/**
* @private
* @type {Object}
*/
documents = {
root: newDoc(),
};
};
let currentDocument = documents.root;
let startEndCount = 0;
let dividerCnt = 0;
/**
* @private
* @type {Object}
*/
currentDocument = this.documents.root;
/**
* @private
* @type {number}
*/
startEndCount = 0;
/**
* @private
* @type {number}
*/
dividerCnt = 0;
export const lineType = {
LINE: 0,
DOTTED_LINE: 1,
};
export const relationType = {
static relationType = {
AGGREGATION: 0,
EXTENSION: 1,
COMPOSITION: 2,
DEPENDENCY: 3,
};
};
const clone = (o) => JSON.parse(JSON.stringify(o));
const setRootDoc = (o) => {
setRootDoc(o) {
log.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o };
rootDoc = o;
};
this.rootDoc = o;
if (this.version === 1) {
this.extract(o);
} else {
this.extract(this.getRootDocV2());
}
}
const getRootDoc = () => rootDoc;
getRootDoc() {
return this.rootDoc;
}
const docTranslator = (parent, node, first) => {
/**
* @private
* @param {Object} parent
* @param {Object} node
* @param {boolean} first
*/
docTranslator(parent, node, first) {
if (node.stmt === STMT_RELATION) {
docTranslator(parent, node.state1, true);
docTranslator(parent, node.state2, false);
this.docTranslator(parent, node.state1, true);
this.docTranslator(parent, node.state2, false);
} else {
if (node.stmt === STMT_STATE) {
if (node.id === '[*]') {
@@ -115,7 +176,6 @@ const docTranslator = (parent, node, first) => {
let i;
for (i = 0; i < node.doc.length; i++) {
if (node.doc[i].type === DIVIDER_TYPE) {
// debugger;
const newNode = clone(node.doc[i]);
newNode.doc = clone(currentDoc);
doc.push(newNode);
@@ -137,17 +197,21 @@ const docTranslator = (parent, node, first) => {
node.doc = doc;
}
node.doc.forEach((docNode) => docTranslator(node, docNode, true));
node.doc.forEach((docNode) => this.docTranslator(node, docNode, true));
}
}
}
};
const getRootDocV2 = () => {
docTranslator({ id: 'root' }, { id: 'root', doc: rootDoc }, true);
return { id: 'root', doc: rootDoc };
// Here
};
/**
/**
* @private
*/
getRootDocV2() {
this.docTranslator({ id: 'root' }, { id: 'root', doc: this.rootDoc }, true);
return { id: 'root', doc: this.rootDoc };
// Here
}
/**
* Convert all of the statements (stmts) that were parsed into states and relationships.
* This is done because a state diagram may have nested sections,
* where each section is a 'document' and has its own set of statements.
@@ -155,10 +219,10 @@ const getRootDocV2 = () => {
* refer to the fork as a whole (document).
* See the parser grammar: the definition of a document is a document then a 'line', where a line can be a statement.
* This will push the statement into the list of statements for the current document.
*
* @private
* @param _doc
*/
const extract = (_doc) => {
extract(_doc) {
// const res = { states: [], relations: [] };
let doc;
if (_doc.doc) {
@@ -171,7 +235,7 @@ const extract = (_doc) => {
// doc = root;
// }
log.info(doc);
clear(true);
this.clear(true);
log.info('Extract initial document:', doc);
@@ -179,7 +243,7 @@ const extract = (_doc) => {
log.warn('Statement', item.stmt);
switch (item.stmt) {
case STMT_STATE:
addState(
this.addState(
item.id.trim(),
item.type,
item.doc,
@@ -191,38 +255,48 @@ const extract = (_doc) => {
);
break;
case STMT_RELATION:
addRelation(item.state1, item.state2, item.description);
this.addRelation(item.state1, item.state2, item.description);
break;
case STMT_CLASSDEF:
addStyleClass(item.id.trim(), item.classes);
this.addStyleClass(item.id.trim(), item.classes);
break;
case STMT_STYLEDEF:
{
const ids = item.id.trim().split(',');
const styles = item.styleClass.split(',');
ids.forEach((id) => {
let foundState = getState(id);
let foundState = this.getState(id);
if (foundState === undefined) {
const trimmedId = id.trim();
addState(trimmedId);
foundState = getState(trimmedId);
this.addState(trimmedId);
foundState = this.getState(trimmedId);
}
foundState.styles = styles.map((s) => s.replace(/;/g, '')?.trim());
});
}
break;
case STMT_APPLYCLASS:
setCssClass(item.id.trim(), item.styleClass);
this.setCssClass(item.id.trim(), item.styleClass);
break;
}
});
const diagramStates = getStates();
const diagramStates = this.getStates();
const config = getConfig();
const look = config.look;
resetDataFetching();
dataFetcher(undefined, getRootDocV2(), diagramStates, nodes, edges, true, look, classes);
nodes.forEach((node) => {
dataFetcher(
undefined,
this.getRootDocV2(),
diagramStates,
this.nodes,
this.edges,
true,
look,
this.classes
);
this.nodes.forEach((node) => {
if (Array.isArray(node.label)) {
// add the rest as description
node.description = node.label.slice(1);
@@ -237,9 +311,9 @@ const extract = (_doc) => {
node.label = node.label[0];
}
});
};
}
/**
/**
* Function called by parser when a node definition has been found.
*
* @param {null | string} id
@@ -251,7 +325,7 @@ const extract = (_doc) => {
* @param {null | string | string[]} styles - styles to apply to this state. Can be a string (1 style) or an array of styles. If it's just 1 style, convert it to an array of that 1 style.
* @param {null | string | string[]} textStyles - text styles to apply to this state. Can be a string (1 text test) or an array of text styles. If it's just 1 text style, convert it to an array of that 1 text style.
*/
export const addState = function (
addState(
id,
type = DEFAULT_STATE_TYPE,
doc = null,
@@ -260,12 +334,12 @@ export const addState = function (
classes = null,
styles = null,
textStyles = null
) {
) {
const trimmedId = id?.trim();
// add the state if needed
if (!currentDocument.states.has(trimmedId)) {
if (!this.currentDocument.states.has(trimmedId)) {
log.info('Adding state ', trimmedId, descr);
currentDocument.states.set(trimmedId, {
this.currentDocument.states.set(trimmedId, {
id: trimmedId,
descriptions: [],
type,
@@ -276,27 +350,27 @@ export const addState = function (
textStyles: [],
});
} else {
if (!currentDocument.states.get(trimmedId).doc) {
currentDocument.states.get(trimmedId).doc = doc;
if (!this.currentDocument.states.get(trimmedId).doc) {
this.currentDocument.states.get(trimmedId).doc = doc;
}
if (!currentDocument.states.get(trimmedId).type) {
currentDocument.states.get(trimmedId).type = type;
if (!this.currentDocument.states.get(trimmedId).type) {
this.currentDocument.states.get(trimmedId).type = type;
}
}
if (descr) {
log.info('Setting state description', trimmedId, descr);
if (typeof descr === 'string') {
addDescription(trimmedId, descr.trim());
this.addDescription(trimmedId, descr.trim());
}
if (typeof descr === 'object') {
descr.forEach((des) => addDescription(trimmedId, des.trim()));
descr.forEach((des) => this.addDescription(trimmedId, des.trim()));
}
}
if (note) {
const doc2 = currentDocument.states.get(trimmedId);
const doc2 = this.currentDocument.states.get(trimmedId);
doc2.note = note;
doc2.note.text = common.sanitizeText(doc2.note.text, getConfig());
}
@@ -304,123 +378,126 @@ export const addState = function (
if (classes) {
log.info('Setting state classes', trimmedId, classes);
const classesList = typeof classes === 'string' ? [classes] : classes;
classesList.forEach((cssClass) => setCssClass(trimmedId, cssClass.trim()));
classesList.forEach((cssClass) => this.setCssClass(trimmedId, cssClass.trim()));
}
if (styles) {
log.info('Setting state styles', trimmedId, styles);
const stylesList = typeof styles === 'string' ? [styles] : styles;
stylesList.forEach((style) => setStyle(trimmedId, style.trim()));
stylesList.forEach((style) => this.setStyle(trimmedId, style.trim()));
}
if (textStyles) {
log.info('Setting state styles', trimmedId, styles);
const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles;
textStylesList.forEach((textStyle) => setTextStyle(trimmedId, textStyle.trim()));
textStylesList.forEach((textStyle) => this.setTextStyle(trimmedId, textStyle.trim()));
}
}
};
export const clear = function (saveCommon) {
nodes = [];
edges = [];
documents = {
clear(saveCommon) {
this.nodes = [];
this.edges = [];
this.documents = {
root: newDoc(),
};
currentDocument = documents.root;
this.currentDocument = this.documents.root;
// number of start and end nodes; used to construct ids
startEndCount = 0;
classes = newClassesList();
this.startEndCount = 0;
this.classes = newClassesList();
if (!saveCommon) {
commonClear();
}
};
}
export const getState = function (id) {
return currentDocument.states.get(id);
};
getState(id) {
return this.currentDocument.states.get(id);
}
getStates() {
return this.currentDocument.states;
}
logDocuments() {
log.info('Documents = ', this.documents);
}
getRelations() {
return this.currentDocument.relations;
}
export const getStates = function () {
return currentDocument.states;
};
export const logDocuments = function () {
log.info('Documents = ', documents);
};
export const getRelations = function () {
return currentDocument.relations;
};
/**
/**
* If the id is a start node ( [*] ), then return a new id constructed from
* the start node name and the current start node count.
* else return the given id
*
* @param {string} id
* @returns {string} - the id (original or constructed)
* @private
*/
function startIdIfNeeded(id = '') {
startIdIfNeeded(id = '') {
let fixedId = id;
if (id === START_NODE) {
startEndCount++;
fixedId = `${START_TYPE}${startEndCount}`;
this.startEndCount++;
fixedId = `${START_TYPE}${this.startEndCount}`;
}
return fixedId;
}
}
/**
/**
* If the id is a start node ( [*] ), then return the start type ('start')
* else return the given type
*
* @param {string} id
* @param {string} type
* @returns {string} - the type that should be used
* @private
*/
function startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
startTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === START_NODE ? START_TYPE : type;
}
}
/**
/**
* If the id is an end node ( [*] ), then return a new id constructed from
* the end node name and the current start_end node count.
* else return the given id
*
* @param {string} id
* @returns {string} - the id (original or constructed)
* @private
*/
function endIdIfNeeded(id = '') {
endIdIfNeeded(id = '') {
let fixedId = id;
if (id === END_NODE) {
startEndCount++;
fixedId = `${END_TYPE}${startEndCount}`;
this.startEndCount++;
fixedId = `${END_TYPE}${this.startEndCount}`;
}
return fixedId;
}
}
/**
/**
* If the id is an end node ( [*] ), then return the end type
* else return the given type
*
* @param {string} id
* @param {string} type
* @returns {string} - the type that should be used
* @private
*/
function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
return id === END_NODE ? END_TYPE : type;
}
}
/**
/**
*
* @param item1
* @param item2
* @param relationTitle
*/
export function addRelationObjs(item1, item2, relationTitle) {
let id1 = startIdIfNeeded(item1.id.trim());
let type1 = startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = startIdIfNeeded(item2.id.trim());
let type2 = startTypeIfNeeded(item2.id.trim(), item2.type);
addRelationObjs(item1, item2, relationTitle) {
let id1 = this.startIdIfNeeded(item1.id.trim());
let type1 = this.startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = this.startIdIfNeeded(item2.id.trim());
let type2 = this.startTypeIfNeeded(item2.id.trim(), item2.type);
addState(
this.addState(
id1,
type1,
item1.doc,
@@ -430,7 +507,7 @@ export function addRelationObjs(item1, item2, relationTitle) {
item1.styles,
item1.textStyles
);
addState(
this.addState(
id2,
type2,
item2.doc,
@@ -441,71 +518,71 @@ export function addRelationObjs(item1, item2, relationTitle) {
item2.textStyles
);
currentDocument.relations.push({
this.currentDocument.relations.push({
id1,
id2,
relationTitle: common.sanitizeText(relationTitle, getConfig()),
});
}
}
/**
/**
* Add a relation between two items. The items may be full objects or just the string id of a state.
*
* @param {string | object} item1
* @param {string | object} item2
* @param {string} title
*/
export const addRelation = function (item1, item2, title) {
addRelation(item1, item2, title) {
if (typeof item1 === 'object') {
addRelationObjs(item1, item2, title);
this.addRelationObjs(item1, item2, title);
} else {
const id1 = startIdIfNeeded(item1.trim());
const type1 = startTypeIfNeeded(item1);
const id2 = endIdIfNeeded(item2.trim());
const type2 = endTypeIfNeeded(item2);
const id1 = this.startIdIfNeeded(item1.trim());
const type1 = this.startTypeIfNeeded(item1);
const id2 = this.endIdIfNeeded(item2.trim());
const type2 = this.endTypeIfNeeded(item2);
addState(id1, type1);
addState(id2, type2);
currentDocument.relations.push({
this.addState(id1, type1);
this.addState(id2, type2);
this.currentDocument.relations.push({
id1,
id2,
title: common.sanitizeText(title, getConfig()),
});
}
};
}
export const addDescription = function (id, descr) {
const theState = currentDocument.states.get(id);
addDescription(id, descr) {
const theState = this.currentDocument.states.get(id);
const _descr = descr.startsWith(':') ? descr.replace(':', '').trim() : descr;
theState.descriptions.push(common.sanitizeText(_descr, getConfig()));
};
}
export const cleanupLabel = function (label) {
cleanupLabel(label) {
if (label.substring(0, 1) === ':') {
return label.substr(2).trim();
} else {
return label.trim();
}
};
}
const getDividerId = () => {
dividerCnt++;
return 'divider-id-' + dividerCnt;
};
getDividerId() {
this.dividerCnt++;
return 'divider-id-' + this.dividerCnt;
}
/**
/**
* Called when the parser comes across a (style) class definition
* @example classDef my-style fill:#f96;
*
* @param {string} id - the id of this (style) class
* @param {string | null} styleAttributes - the string with 1 or more style attributes (each separated by a comma)
*/
export const addStyleClass = function (id, styleAttributes = '') {
addStyleClass(id, styleAttributes = '') {
// create a new style class object with this id
if (!classes.has(id)) {
classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef
if (!this.classes.has(id)) {
this.classes.set(id, { id: id, styles: [], textStyles: [] }); // This is a classDef
}
const foundClass = classes.get(id);
const foundClass = this.classes.get(id);
if (styleAttributes !== undefined && styleAttributes !== null) {
styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => {
// remove any trailing ;
@@ -520,17 +597,17 @@ export const addStyleClass = function (id, styleAttributes = '') {
foundClass.styles.push(fixedAttrib);
});
}
};
}
/**
/**
* Return all of the style classes
* @returns {{} | any | classes}
*/
export const getClasses = function () {
return classes;
};
getClasses() {
return this.classes;
}
/**
/**
* Add a (style) class or css class to a state with the given id.
* If the state isn't already in the list of known states, add it.
* Might be called by parser when a style class or CSS class should be applied to a state
@@ -538,19 +615,19 @@ export const getClasses = function () {
* @param {string | string[]} itemIds The id or a list of ids of the item(s) to apply the css class to
* @param {string} cssClassName CSS class name
*/
export const setCssClass = function (itemIds, cssClassName) {
itemIds.split(',').forEach(function (id) {
let foundState = getState(id);
setCssClass(itemIds, cssClassName) {
itemIds.split(',').forEach((id) => {
let foundState = this.getState(id);
if (foundState === undefined) {
const trimmedId = id.trim();
addState(trimmedId);
foundState = getState(trimmedId);
this.addState(trimmedId);
foundState = this.getState(trimmedId);
}
foundState.classes.push(cssClassName);
});
};
}
/**
/**
* Add a style to a state with the given id.
* @example style stateId fill:#f9f,stroke:#333,stroke-width:4px
* where 'style' is the keyword
@@ -560,67 +637,70 @@ export const setCssClass = function (itemIds, cssClassName) {
* @param itemId The id of item to apply the style to
* @param styleText - the text of the attributes for the style
*/
export const setStyle = function (itemId, styleText) {
const item = getState(itemId);
setStyle(itemId, styleText) {
const item = this.getState(itemId);
if (item !== undefined) {
item.styles.push(styleText);
}
};
}
/**
/**
* Add a text style to a state with the given id
*
* @param itemId The id of item to apply the css class to
* @param cssClassName CSS class name
*/
export const setTextStyle = function (itemId, cssClassName) {
const item = getState(itemId);
setTextStyle(itemId, cssClassName) {
const item = this.getState(itemId);
if (item !== undefined) {
item.textStyles.push(cssClassName);
}
};
}
const getDirection = () => direction;
const setDirection = (dir) => {
direction = dir;
};
/**
* Finds the direction statement in the root document.
* @private
* @returns {{ value: string } | undefined} - the direction statement if present
*/
getDirectionStatement() {
return this.rootDoc.find((doc) => doc.stmt === STMT_DIRECTION);
}
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
getDirection() {
return this.getDirectionStatement()?.value ?? DEFAULT_DIAGRAM_DIRECTION;
}
export const getData = () => {
setDirection(dir) {
const doc = this.getDirectionStatement();
if (doc) {
doc.value = dir;
} else {
this.rootDoc.unshift({ stmt: STMT_DIRECTION, value: dir });
}
}
trimColon(str) {
return str && str[0] === ':' ? str.substr(1).trim() : str.trim();
}
getData() {
const config = getConfig();
return { nodes, edges, other: {}, config, direction: getDir(getRootDocV2()) };
};
return {
nodes: this.nodes,
edges: this.edges,
other: {},
config,
direction: getDir(this.getRootDocV2()),
};
}
export default {
getConfig: () => getConfig().state,
getData,
addState,
clear,
getState,
getStates,
getRelations,
getClasses,
getDirection,
addRelation,
getDividerId,
setDirection,
cleanupLabel,
lineType,
relationType,
logDocuments,
getRootDoc,
setRootDoc,
getRootDocV2,
extract,
trimColon,
getAccTitle,
setAccTitle,
getAccDescription,
setAccDescription,
addStyleClass,
setCssClass,
addDescription,
setDiagramTitle,
getDiagramTitle,
};
getConfig() {
return getConfig().state;
}
getAccTitle = getAccTitle;
setAccTitle = setAccTitle;
getAccDescription = getAccDescription;
setAccDescription = setAccDescription;
setDiagramTitle = setDiagramTitle;
getDiagramTitle = getDiagramTitle;
}

View File

@@ -1,8 +1,9 @@
import stateDb from './stateDb.js';
import { StateDB } from './stateDb.js';
describe('State Diagram stateDb', () => {
let stateDb;
beforeEach(() => {
stateDb.clear();
stateDb = new StateDB(1);
});
describe('addStyleClass', () => {
@@ -20,8 +21,9 @@ describe('State Diagram stateDb', () => {
});
describe('addDescription to a state', () => {
let stateDb;
beforeEach(() => {
stateDb.clear();
stateDb = new StateDB(1);
stateDb.addState('state1');
});
@@ -73,3 +75,25 @@ describe('State Diagram stateDb', () => {
});
});
});
describe('state db class', () => {
let stateDb;
beforeEach(() => {
stateDb = new StateDB(1);
});
// This is to ensure that functions used in state JISON are exposed as function from StateDb
it('should have functions used in flow JISON as own property', () => {
const functionsUsedInParser = [
'setRootDoc',
'trimColon',
'getDividerId',
'setAccTitle',
'setAccDescription',
'setDirection',
];
for (const fun of functionsUsedInParser) {
expect(Object.hasOwn(stateDb, fun)).toBe(true);
}
});
});

View File

@@ -1,11 +1,13 @@
import { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js';
import stateDiagram from './parser/stateDiagram.jison';
import stateDiagram, { parser } from './parser/stateDiagram.jison';
import { DEFAULT_DIAGRAM_DIRECTION } from './stateCommon.js';
import { StateDB } from './stateDb.js';
describe('state diagram V2, ', function () {
// TODO - these examples should be put into ./parser/stateDiagram.spec.js
describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () {
stateDb = new StateDB(2);
parser.yy = stateDb;
stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear();
@@ -127,7 +129,6 @@ describe('state diagram V2, ', function () {
`;
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const rels = stateDb.getRelations();
const rel_1_2 = rels.find((rel) => rel.id1 === 'State1' && rel.id2 === 'State2');
@@ -402,7 +403,6 @@ describe('state diagram V2, ', function () {
`;
stateDiagram.parser.parse(diagram);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDb.getStates();
expect(states.get('Active').doc[0].id).toEqual('Idle');
@@ -413,5 +413,34 @@ describe('state diagram V2, ', function () {
const rel_Active_Active = rels.find((rel) => rel.id1 === 'Active' && rel.id2 === 'Active');
expect(rel_Active_Active.relationTitle).toEqual('LOG');
});
it('should check default diagram direction', () => {
const diagram = `
stateDiagram
[*] --> Still
Still --> [*]
`;
parser.parse(diagram);
// checking default direction if no direction is specified
const defaultDir = stateDb.getDirection();
expect(defaultDir).toEqual(DEFAULT_DIAGRAM_DIRECTION);
});
it('retrieve the diagram direction correctly', () => {
const diagram = `
stateDiagram
direction LR
[*] --> Still
Still --> [*]
`;
parser.parse(diagram);
//retrieve the diagram direction
const currentDirection = stateDb.getDirection();
expect(currentDirection).toEqual('LR');
});
});
});

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison';
import db from './stateDb.js';
import { StateDB } from './stateDb.js';
import styles from './styles.js';
import renderer from './stateRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,
db,
get db() {
return new StateDB(2);
},
renderer,
styles,
init: (cnf) => {

View File

@@ -1,9 +1,11 @@
import { parser } from './parser/stateDiagram.jison';
import stateDb from './stateDb.js';
import { StateDB } from './stateDb.js';
describe('state diagram, ', function () {
describe('when parsing an info graph it', function () {
let stateDb;
beforeEach(function () {
stateDb = new StateDB(1);
parser.yy = stateDb;
});

View File

@@ -1,13 +1,15 @@
import type { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: JISON doesn't support types
import parser from './parser/stateDiagram.jison';
import db from './stateDb.js';
import { StateDB } from './stateDb.js';
import styles from './styles.js';
import renderer from './stateRenderer.js';
export const diagram: DiagramDefinition = {
parser,
db,
get db() {
return new StateDB(1);
},
renderer,
styles,
init: (cnf) => {

View File

@@ -36,7 +36,6 @@ export const getClasses = function (
text: string,
diagramObj: any
): Map<string, DiagramStyleClassDef> {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
return diagramObj.db.getClasses();
};

View File

@@ -136,7 +136,6 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) =
return {};
});
diagObj.db.extract(doc);
const states = diagObj.db.getStates();
const relations = diagObj.db.getRelations();

View File

@@ -67,10 +67,12 @@ vi.mock('stylis', () => {
import { compile, serialize } from 'stylis';
import { Diagram } from './Diagram.js';
import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js';
import { ClassDB } from './diagrams/class/classDb.js';
import { FlowDB } from './diagrams/flowchart/flowDb.js';
import { SequenceDB } from './diagrams/sequence/sequenceDb.js';
import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js';
import { StateDB } from './diagrams/state/stateDb.js';
/**
* @see https://vitest.dev/guide/mocking.html Mock part of a module
@@ -836,6 +838,31 @@ graph TD;A--x|text including URL space|B;`)
});
it('should not modify db when rendering different diagrams', async () => {
const stateDiagram1 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction LR
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
const stateDiagram2 = await mermaidAPI.getDiagramFromText(
`stateDiagram
direction TB
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]`
);
expect(stateDiagram1.db).not.toBe(stateDiagram2.db);
assert(stateDiagram1.db instanceof StateDB);
assert(stateDiagram2.db instanceof StateDB);
expect(stateDiagram1.db.getDirection()).not.toEqual(stateDiagram2.db.getDirection());
const flowDiagram1 = await mermaidAPI.getDiagramFromText(
`flowchart LR
A -- text --> B -- text2 --> C`
@@ -899,28 +926,18 @@ graph TD;A--x|text including URL space|B;`)
);
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
actor A1
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!`
);
// Since sequenceDiagram will return same Db object each time, we can compare the db to be same.
expect(sequenceDiagram1.db).toBe(sequenceDiagram2.db);
});
});
// Sequence Diagram currently uses a singleton DB, so this test will fail
it.fails('should not modify db when rendering different sequence diagrams', async () => {
const sequenceDiagram1 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
// Since sequenceDiagram will return new Db object each time, we can compare the db to be different.
expect(sequenceDiagram1.db).not.toBe(sequenceDiagram2.db);
assert(sequenceDiagram1.db instanceof SequenceDB);
assert(sequenceDiagram2.db instanceof SequenceDB);
expect(sequenceDiagram1.db.getActors()).not.toEqual(sequenceDiagram2.db.getActors());
});
});
});

2433
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff