mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge branch 'master' into latest-news
This commit is contained in:
commit
b26449f338
@ -38,6 +38,10 @@ module.exports = {
|
||||
'lodash',
|
||||
'unicorn',
|
||||
],
|
||||
ignorePatterns: [
|
||||
// this file is automatically generated by `pnpm run --filter mermaid types:build-config`
|
||||
'packages/mermaid/src/config.type.ts',
|
||||
],
|
||||
rules: {
|
||||
curly: 'error',
|
||||
'no-console': 'error',
|
||||
@ -123,6 +127,14 @@ module.exports = {
|
||||
files: ['*.{ts,tsx}'],
|
||||
plugins: ['tsdoc'],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector: 'TSEnumDeclaration',
|
||||
message:
|
||||
'Prefer using TypeScript union types over TypeScript enum, since TypeScript enums have a bunch of issues, see https://dev.to/dvddpl/whats-the-problem-with-typescript-enums-2okj',
|
||||
},
|
||||
],
|
||||
'tsdoc/syntax': 'error',
|
||||
},
|
||||
},
|
||||
|
9
codecov.yaml → .github/codecov.yaml
vendored
9
codecov.yaml → .github/codecov.yaml
vendored
@ -1,3 +1,6 @@
|
||||
codecov:
|
||||
branch: develop
|
||||
|
||||
comment:
|
||||
layout: 'reach, diff, flags, files'
|
||||
behavior: default
|
||||
@ -8,5 +11,7 @@ comment:
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 1%
|
||||
off
|
||||
# Turing off for now as code coverage isn't stable and causes unnecessary build failures.
|
||||
# default:
|
||||
# threshold: 2%
|
7
.github/pr-labeler.yml
vendored
7
.github/pr-labeler.yml
vendored
@ -1,3 +1,4 @@
|
||||
'Type: Bug / Error': 'bug/*'
|
||||
'Type: Enhancement': 'feature/*'
|
||||
'Type: Other': 'other/*'
|
||||
'Type: Bug / Error': ['bug/*', fix/*]
|
||||
'Type: Enhancement': ['feature/*', 'feat/*']
|
||||
'Type: Other': ['other/*', 'chore/*', 'test/*', 'refactor/*']
|
||||
'Area: Documentation': ['docs/*']
|
||||
|
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@ -13,6 +13,6 @@ Describe the way your implementation works or what design decisions you made if
|
||||
Make sure you
|
||||
|
||||
- [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
|
||||
- [ ] :computer: have added unit/e2e tests (if appropriate)
|
||||
- [ ] :notebook: have added documentation (if appropriate)
|
||||
- [ ] :computer: have added necessary unit/e2e tests.
|
||||
- [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/community/development.md#3-update-documentation) is used for all new features.
|
||||
- [ ] :bookmark: targeted `develop` branch
|
||||
|
12
.github/workflows/build-docs.yml
vendored
12
.github/workflows/build-docs.yml
vendored
@ -1,14 +1,18 @@
|
||||
name: Build Vitepress docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release/*
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -25,5 +29,9 @@ jobs:
|
||||
- name: Install Packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Verify release verion
|
||||
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release')) }}
|
||||
run: pnpm --filter mermaid run docs:verify-version
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm --filter mermaid run docs:build:vitepress
|
||||
|
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -2,6 +2,7 @@ name: Build
|
||||
|
||||
on:
|
||||
push: {}
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
@ -12,7 +13,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-mermaid:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
2
.github/workflows/check-readme-in-sync.yml
vendored
2
.github/workflows/check-readme-in-sync.yml
vendored
@ -14,7 +14,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
check-readme:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
7
.github/workflows/checks.yml
vendored
7
.github/workflows/checks.yml
vendored
@ -1,15 +1,16 @@
|
||||
on:
|
||||
push: {}
|
||||
push:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
name: Static analysis
|
||||
name: Static analysis on Test files
|
||||
|
||||
jobs:
|
||||
test:
|
||||
check-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: check tests
|
||||
if: github.repository_owner == 'mermaid-js'
|
||||
|
2
.github/workflows/e2e-applitools.yml
vendored
2
.github/workflows/e2e-applitools.yml
vendored
@ -19,7 +19,7 @@ env:
|
||||
USE_APPLI: ${{ secrets.APPLITOOLS_API_KEY && 'true' || '' }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
e2e-applitools:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
14
.github/workflows/e2e.yml
vendored
14
.github/workflows/e2e.yml
vendored
@ -1,12 +1,15 @@
|
||||
name: E2E
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@ -42,15 +45,18 @@ jobs:
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
VITEST_COVERAGE: true
|
||||
CYPRESS_COMMIT: ${{ github.sha }}
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
if: steps.cypress.conclusion == 'success'
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
|
||||
with:
|
||||
files: coverage/cypress/lcov.info
|
||||
flags: e2e
|
||||
name: mermaid-codecov
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
token: 6845cc80-77ee-4e17-85a1-026cd95e0766
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() && steps.cypress.conclusion == 'failure' }}
|
||||
|
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@ -1,7 +1,8 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push: {}
|
||||
push:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
@ -52,6 +53,19 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify `./src/config.type.ts` is in sync with `./src/schemas/config.schema.yaml`
|
||||
shell: bash
|
||||
run: |
|
||||
if ! pnpm run --filter mermaid types:verify-config; then
|
||||
ERROR_MESSAGE='Running `pnpm run --filter mermaid types:verify-config` failed.'
|
||||
ERROR_MESSAGE+=' This should be fixed by running'
|
||||
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
|
||||
ERROR_MESSAGE+=' on your local machine.'
|
||||
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify Docs
|
||||
id: verifyDocs
|
||||
working-directory: ./packages/mermaid
|
||||
|
@ -1,11 +1,15 @@
|
||||
name: Validate PR Labeler Configuration
|
||||
on:
|
||||
push: {}
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/pr-labeler-config-validator.yml
|
||||
- .github/workflows/pr-labeler.yml
|
||||
- .github/pr-labeler.yml
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
paths:
|
||||
- .github/workflows/pr-labeler-config-validator.yml
|
||||
- .github/workflows/pr-labeler.yml
|
||||
- .github/pr-labeler.yml
|
||||
|
||||
jobs:
|
||||
pr-labeler:
|
||||
|
6
.github/workflows/publish-docs.yml
vendored
6
.github/workflows/publish-docs.yml
vendored
@ -19,7 +19,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -48,11 +48,11 @@ jobs:
|
||||
path: packages/mermaid/src/vitepress/.vitepress/dist
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
deploy-docs:
|
||||
environment:
|
||||
name: github-pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
needs: build-docs
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
|
@ -6,7 +6,7 @@ on:
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
publish-preview:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
15
.github/workflows/test.yml
vendored
15
.github/workflows/test.yml
vendored
@ -1,12 +1,12 @@
|
||||
name: Unit Tests
|
||||
|
||||
on: [push, pull_request]
|
||||
on: [push, pull_request, merge_group]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@ -43,15 +43,12 @@ jobs:
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||
with:
|
||||
files: ./coverage/vitest/lcov.info
|
||||
flags: unit
|
||||
name: mermaid-codecov
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
# Coveralls is throwing 500. Disabled for now.
|
||||
# - name: Upload Coverage to Coveralls
|
||||
# uses: coverallsapp/github-action@v2
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# flag-name: unit
|
||||
token: 6845cc80-77ee-4e17-85a1-026cd95e0766
|
||||
|
2
.github/workflows/update-browserlist.yml
vendored
2
.github/workflows/update-browserlist.yml
vendored
@ -5,7 +5,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
update-browser-list:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
@ -5,5 +5,8 @@ coverage
|
||||
# Autogenerated by PNPM
|
||||
pnpm-lock.yaml
|
||||
stats
|
||||
packages/mermaid/src/docs/.vitepress/components.d.ts
|
||||
**/.vitepress/components.d.ts
|
||||
**/.vitepress/cache
|
||||
.nyc_output
|
||||
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||
packages/mermaid/src/config.type.ts
|
||||
|
@ -2,6 +2,7 @@ import { build, InlineConfig, type PluginOption } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import jisonPlugin from './jisonPlugin.js';
|
||||
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
@ -121,6 +122,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
},
|
||||
plugins: [
|
||||
jisonPlugin(),
|
||||
jsonSchemaPlugin(), // handles `.schema.yaml` files
|
||||
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
||||
typescript({ compilerOptions: { declaration: false } }),
|
||||
istanbul({
|
||||
|
150
.vite/jsonSchemaPlugin.ts
Normal file
150
.vite/jsonSchemaPlugin.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||
import assert from 'node:assert';
|
||||
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||
import { PluginOption } from 'vite';
|
||||
|
||||
import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';
|
||||
|
||||
/**
|
||||
* All of the keys in the mermaid config that have a mermaid diagram config.
|
||||
*/
|
||||
const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
||||
'flowchart',
|
||||
'sequence',
|
||||
'gantt',
|
||||
'journey',
|
||||
'class',
|
||||
'state',
|
||||
'er',
|
||||
'pie',
|
||||
'quadrantChart',
|
||||
'requirement',
|
||||
'mindmap',
|
||||
'timeline',
|
||||
'gitGraph',
|
||||
'c4',
|
||||
'sankey',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Generate default values from the JSON Schema.
|
||||
*
|
||||
* AJV does not support nested default values yet (or default values with $ref),
|
||||
* so we need to manually find them (this may be fixed in ajv v9).
|
||||
*
|
||||
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
|
||||
* @returns The default mermaid config object.
|
||||
*/
|
||||
function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
|
||||
const ajv = new Ajv2019({
|
||||
useDefaults: true,
|
||||
allowUnionTypes: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
ajv.addKeyword({
|
||||
keyword: 'meta:enum', // used by jsonschema2md
|
||||
errors: false,
|
||||
});
|
||||
ajv.addKeyword({
|
||||
keyword: 'tsType', // used by json-schema-to-typescript
|
||||
errors: false,
|
||||
});
|
||||
|
||||
// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
|
||||
// (may be fixed in v9) so we need to manually use sub-schemas
|
||||
const mermaidDefaultConfig = {};
|
||||
|
||||
assert.ok(mermaidConfigSchema.$defs);
|
||||
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;
|
||||
|
||||
for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
|
||||
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
|
||||
const [root, defs, defName] = subSchemaRef.split('/');
|
||||
assert.strictEqual(root, '#');
|
||||
assert.strictEqual(defs, '$defs');
|
||||
const subSchema = {
|
||||
$schema: mermaidConfigSchema.$schema,
|
||||
$defs: mermaidConfigSchema.$defs,
|
||||
...mermaidConfigSchema.$defs[defName],
|
||||
} as JSONSchemaType<BaseDiagramConfig>;
|
||||
|
||||
const validate = ajv.compile(subSchema);
|
||||
|
||||
mermaidDefaultConfig[key] = {};
|
||||
|
||||
for (const required of subSchema.required ?? []) {
|
||||
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
|
||||
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
|
||||
}
|
||||
}
|
||||
if (!validate(mermaidDefaultConfig[key])) {
|
||||
throw new Error(
|
||||
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
|
||||
validate.errors,
|
||||
undefined,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const validate = ajv.compile(mermaidConfigSchema);
|
||||
|
||||
if (!validate(mermaidDefaultConfig)) {
|
||||
throw new Error(
|
||||
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
|
||||
validate.errors,
|
||||
undefined,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return mermaidDefaultConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
|
||||
*
|
||||
* Use `my-example.schema.yaml?only-defaults=true` to only load the default values.
|
||||
*/
|
||||
export default function jsonSchemaPlugin(): PluginOption {
|
||||
return {
|
||||
name: 'json-schema-plugin',
|
||||
transform(src: string, id: string) {
|
||||
const idAsUrl = new URL(id, 'file:///');
|
||||
|
||||
if (!idAsUrl.pathname.endsWith('schema.yaml')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idAsUrl.searchParams.get('only-defaults')) {
|
||||
const jsonSchema = load(src, {
|
||||
filename: idAsUrl.pathname,
|
||||
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||
schema: JSON_SCHEMA,
|
||||
}) as JSONSchemaType<MermaidConfig>;
|
||||
return {
|
||||
code: `export default ${JSON.stringify(generateDefaults(jsonSchema), undefined, 2)};`,
|
||||
map: null, // no source map
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
code: `export default ${JSON.stringify(
|
||||
load(src, {
|
||||
filename: idAsUrl.pathname,
|
||||
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||
schema: JSON_SCHEMA,
|
||||
}),
|
||||
undefined,
|
||||
2
|
||||
)};`,
|
||||
map: null, // provide source map if available
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -17,7 +17,7 @@
|
||||
"name": "Docs generation",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/docs.mts"],
|
||||
"args": ["scripts/docs.cli.mts"],
|
||||
"runtimeArgs": ["--loader", "ts-node/esm"],
|
||||
"cwd": "${workspaceRoot}/packages/mermaid",
|
||||
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
|
||||
|
13
__mocks__/sankeyRenderer.js
Normal file
13
__mocks__/sankeyRenderer.js
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Mocked Sankey diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@ -89,6 +89,8 @@
|
||||
"mult",
|
||||
"neurodiverse",
|
||||
"nextra",
|
||||
"nikolay",
|
||||
"nirname",
|
||||
"orlandoni",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
@ -102,10 +104,13 @@
|
||||
"ranksep",
|
||||
"rect",
|
||||
"rects",
|
||||
"reda",
|
||||
"redmine",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"rozhkov",
|
||||
"sandboxed",
|
||||
"sankey",
|
||||
"setupgraphviewbox",
|
||||
"shiki",
|
||||
"sidharth",
|
||||
@ -120,6 +125,7 @@
|
||||
"stylis",
|
||||
"subhash-halder",
|
||||
"substate",
|
||||
"sulais",
|
||||
"sveidqvist",
|
||||
"swimm",
|
||||
"techn",
|
||||
@ -130,6 +136,7 @@
|
||||
"tsdoc",
|
||||
"tuleap",
|
||||
"tylerlong",
|
||||
"typora",
|
||||
"ugge",
|
||||
"unist",
|
||||
"unocss",
|
||||
@ -143,6 +150,7 @@
|
||||
"vueuse",
|
||||
"xlink",
|
||||
"yash",
|
||||
"yokozuna",
|
||||
"zenuml"
|
||||
],
|
||||
"patterns": [
|
||||
|
@ -1,93 +0,0 @@
|
||||
const utf8ToB64 = (str) => {
|
||||
return window.btoa(unescape(encodeURIComponent(str)));
|
||||
};
|
||||
|
||||
const batchId = 'mermaid-batch' + new Date().getTime();
|
||||
|
||||
export const mermaidUrl = (graphStr, options, api) => {
|
||||
const obj = {
|
||||
code: graphStr,
|
||||
mermaid: options,
|
||||
};
|
||||
const objStr = JSON.stringify(obj);
|
||||
let url = 'http://localhost:9000/e2e.html?graph=' + utf8ToB64(objStr);
|
||||
if (api) {
|
||||
url = 'http://localhost:9000/xss.html?graph=' + graphStr;
|
||||
}
|
||||
|
||||
if (options.listUrl) {
|
||||
cy.log(options.listId, ' ', url);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const imgSnapshotTest = (graphStr, _options = {}, api = false, validation = undefined) => {
|
||||
cy.log(_options);
|
||||
const options = Object.assign(_options);
|
||||
if (!options.fontFamily) {
|
||||
options.fontFamily = 'courier';
|
||||
}
|
||||
if (!options.sequence) {
|
||||
options.sequence = {};
|
||||
}
|
||||
if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
}
|
||||
if (options.sequence && !options.sequence.noteFontFamily) {
|
||||
options.sequence.noteFontFamily = 'courier';
|
||||
}
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
options.sequence.noteFontFamily = 'courier';
|
||||
options.sequence.messageFontFamily = 'courier';
|
||||
if (options.sequence && !options.sequence.actorFontFamily) {
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
}
|
||||
if (!options.fontSize) {
|
||||
options.fontSize = '16px';
|
||||
}
|
||||
const url = mermaidUrl(graphStr, options, api);
|
||||
openURLAndVerifyRendering(url, options, validation);
|
||||
};
|
||||
|
||||
export const urlSnapshotTest = (url, _options, api = false, validation) => {
|
||||
const options = Object.assign(_options);
|
||||
openURLAndVerifyRendering(url, options, validation);
|
||||
};
|
||||
|
||||
export const renderGraph = (graphStr, options, api) => {
|
||||
const url = mermaidUrl(graphStr, options, api);
|
||||
openURLAndVerifyRendering(url, options);
|
||||
};
|
||||
|
||||
export const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||
const useAppli = Cypress.env('useAppli');
|
||||
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||
|
||||
if (useAppli) {
|
||||
cy.log('Opening eyes ' + Cypress.spec.name + ' --- ' + name);
|
||||
cy.eyesOpen({
|
||||
appName: 'Mermaid',
|
||||
testName: name,
|
||||
batchName: Cypress.spec.name,
|
||||
batchId: batchId + Cypress.spec.name,
|
||||
});
|
||||
}
|
||||
|
||||
cy.visit(url);
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('svg').should('be.visible');
|
||||
|
||||
if (validation) {
|
||||
cy.get('svg').should(validation);
|
||||
}
|
||||
|
||||
if (useAppli) {
|
||||
cy.log('Check eyes' + Cypress.spec.name);
|
||||
cy.eyesCheckWindow('Click!');
|
||||
cy.log('Closing eyes' + Cypress.spec.name);
|
||||
cy.eyesClose();
|
||||
} else {
|
||||
cy.matchImageSnapshot(name);
|
||||
}
|
||||
};
|
132
cypress/helpers/util.ts
Normal file
132
cypress/helpers/util.ts
Normal file
@ -0,0 +1,132 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Buffer } from 'buffer';
|
||||
import type { MermaidConfig } from '../../packages/mermaid/src/config.type.js';
|
||||
|
||||
interface CypressConfig {
|
||||
listUrl?: boolean;
|
||||
listId?: string;
|
||||
name?: string;
|
||||
}
|
||||
type CypressMermaidConfig = MermaidConfig & CypressConfig;
|
||||
|
||||
interface CodeObject {
|
||||
code: string;
|
||||
mermaid: CypressMermaidConfig;
|
||||
}
|
||||
|
||||
const utf8ToB64 = (str: string): string => {
|
||||
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
|
||||
};
|
||||
|
||||
const batchId: string = 'mermaid-batch-' + Cypress.env('CYPRESS_COMMIT') || Date.now().toString();
|
||||
|
||||
export const mermaidUrl = (
|
||||
graphStr: string,
|
||||
options: CypressMermaidConfig,
|
||||
api: boolean
|
||||
): string => {
|
||||
const codeObject: CodeObject = {
|
||||
code: graphStr,
|
||||
mermaid: options,
|
||||
};
|
||||
const objStr: string = JSON.stringify(codeObject);
|
||||
let url = `http://localhost:9000/e2e.html?graph=${utf8ToB64(objStr)}`;
|
||||
if (api) {
|
||||
url = `http://localhost:9000/xss.html?graph=${graphStr}`;
|
||||
}
|
||||
|
||||
if (options.listUrl) {
|
||||
cy.log(options.listId, ' ', url);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
export const imgSnapshotTest = (
|
||||
graphStr: string,
|
||||
_options: CypressMermaidConfig = {},
|
||||
api = false,
|
||||
validation?: any
|
||||
): void => {
|
||||
cy.log(JSON.stringify(_options));
|
||||
const options: CypressMermaidConfig = Object.assign(_options);
|
||||
if (!options.fontFamily) {
|
||||
options.fontFamily = 'courier';
|
||||
}
|
||||
if (!options.sequence) {
|
||||
options.sequence = {};
|
||||
}
|
||||
if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
}
|
||||
if (options.sequence && !options.sequence.noteFontFamily) {
|
||||
options.sequence.noteFontFamily = 'courier';
|
||||
}
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
options.sequence.noteFontFamily = 'courier';
|
||||
options.sequence.messageFontFamily = 'courier';
|
||||
if (options.sequence && !options.sequence.actorFontFamily) {
|
||||
options.sequence.actorFontFamily = 'courier';
|
||||
}
|
||||
if (!options.fontSize) {
|
||||
options.fontSize = 16;
|
||||
}
|
||||
|
||||
const url: string = mermaidUrl(graphStr, options, api);
|
||||
openURLAndVerifyRendering(url, options, validation);
|
||||
};
|
||||
|
||||
export const urlSnapshotTest = (
|
||||
url: string,
|
||||
_options: CypressMermaidConfig,
|
||||
_api = false,
|
||||
validation?: any
|
||||
): void => {
|
||||
const options: CypressMermaidConfig = Object.assign(_options);
|
||||
openURLAndVerifyRendering(url, options, validation);
|
||||
};
|
||||
|
||||
export const renderGraph = (
|
||||
graphStr: string,
|
||||
options: CypressMermaidConfig = {},
|
||||
api = false
|
||||
): void => {
|
||||
const url: string = mermaidUrl(graphStr, options, api);
|
||||
openURLAndVerifyRendering(url, options);
|
||||
};
|
||||
|
||||
export const openURLAndVerifyRendering = (
|
||||
url: string,
|
||||
options: CypressMermaidConfig,
|
||||
validation?: any
|
||||
): void => {
|
||||
const useAppli: boolean = Cypress.env('useAppli');
|
||||
const name: string = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||
|
||||
if (useAppli) {
|
||||
cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
|
||||
cy.eyesOpen({
|
||||
appName: 'Mermaid',
|
||||
testName: name,
|
||||
batchName: Cypress.spec.name,
|
||||
batchId: batchId + Cypress.spec.name,
|
||||
});
|
||||
}
|
||||
|
||||
cy.visit(url);
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('svg').should('be.visible');
|
||||
|
||||
if (validation) {
|
||||
cy.get('svg').should(validation);
|
||||
}
|
||||
|
||||
if (useAppli) {
|
||||
cy.log(`Check eyes ${Cypress.spec.name}`);
|
||||
cy.eyesCheckWindow('Click!');
|
||||
cy.log(`Closing eyes ${Cypress.spec.name}`);
|
||||
cy.eyesClose();
|
||||
} else {
|
||||
cy.matchImageSnapshot(name);
|
||||
}
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { renderGraph } from '../../helpers/util.js';
|
||||
import { renderGraph } from '../../helpers/util.ts';
|
||||
describe('Configuration', () => {
|
||||
describe('arrowMarkerAbsolute', () => {
|
||||
it('should handle default value false of arrowMarkerAbsolute', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { urlSnapshotTest } from '../../helpers/util.js';
|
||||
import { urlSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('mermaid', () => {
|
||||
describe('registerDiagram', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.js';
|
||||
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts';
|
||||
|
||||
describe('CSS injections', () => {
|
||||
it('should not allow CSS injections outside of the diagram', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { mermaidUrl } from '../../helpers/util.js';
|
||||
import { mermaidUrl } from '../../helpers/util.ts';
|
||||
describe('XSS', () => {
|
||||
it('should handle xss in tags', () => {
|
||||
const str =
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Git Graph diagram', () => {
|
||||
it('1: should render a simple gitgraph with commit on main branch', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('C4 diagram', () => {
|
||||
it('should render a simple C4Context diagram', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
describe('Class diagram V2', () => {
|
||||
it('0: should render a simple class diagram', () => {
|
||||
imgSnapshotTest(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Class diagram', () => {
|
||||
it('1: should render a simple class diagram', () => {
|
||||
@ -423,4 +423,82 @@ describe('Class diagram', () => {
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render class diagram with newlines in title', () => {
|
||||
imgSnapshotTest(`
|
||||
classDiagram
|
||||
Animal <|-- \`Du\nck\`
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class \`Du\nck\` {
|
||||
+String beakColor
|
||||
+String featherColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
`);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render class diagram with many newlines in title', () => {
|
||||
imgSnapshotTest(`
|
||||
classDiagram
|
||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||
+String Also
|
||||
-Stirng Many
|
||||
#int Members
|
||||
+And()
|
||||
-Many()
|
||||
#Methods()
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should render with newlines in title and an annotation', () => {
|
||||
imgSnapshotTest(`
|
||||
classDiagram
|
||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||
+String Also
|
||||
-Stirng Many
|
||||
#int Members
|
||||
+And()
|
||||
-Many()
|
||||
#Methods()
|
||||
}
|
||||
<<Interface>> \`This\nTitle\nHas\nMany\nNewlines\`
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle newline title in namespace', () => {
|
||||
imgSnapshotTest(`
|
||||
classDiagram
|
||||
namespace testingNamespace {
|
||||
class \`This\nTitle\nHas\nMany\nNewlines\` {
|
||||
+String Also
|
||||
-Stirng Many
|
||||
#int Members
|
||||
+And()
|
||||
-Many()
|
||||
#Methods()
|
||||
}
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should handle newline in string label', () => {
|
||||
imgSnapshotTest(`
|
||||
classDiagram
|
||||
class A["This has\na newline!"] {
|
||||
+String boop
|
||||
-Int beep
|
||||
#double bop
|
||||
}
|
||||
|
||||
class B["This title also has\na newline"]
|
||||
B : +with(more)
|
||||
B : -methods()
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Configuration and directives - nodes should be light blue', () => {
|
||||
it('No config - use default', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Current diagram', () => {
|
||||
it('should render a state with states in it', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Flowchart', () => {
|
||||
it('34: testing the label width in percy', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Entity Relationship Diagram', () => {
|
||||
it('should render a simple ER diagram', () => {
|
||||
@ -200,6 +200,27 @@ describe('Entity Relationship Diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render entities with attributes that begin with asterisk', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
BOOK {
|
||||
int *id
|
||||
string name
|
||||
varchar(99) summary
|
||||
}
|
||||
BOOK }o..o{ STORE : soldBy
|
||||
STORE {
|
||||
int *id
|
||||
string name
|
||||
varchar(50) address
|
||||
}
|
||||
`,
|
||||
{ loglevel: 1 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render entities with keys', () => {
|
||||
renderGraph(
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe.skip('Flowchart ELK', () => {
|
||||
it('1-elk: should render a simple flowchart', () => {
|
||||
@ -681,7 +681,7 @@ title: Simple flowchart
|
||||
flowchart-elk TD
|
||||
A --> B
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('elk: should include classes on the edges', () => {
|
||||
@ -710,7 +710,7 @@ flowchart-elk LR
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
@ -726,7 +726,7 @@ flowchart-elk LR
|
||||
b --> d(The dog in the hog)
|
||||
c --> d
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
@ -736,7 +736,7 @@ flowchart-elk LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
@ -749,7 +749,7 @@ Word!
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`) --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
@ -766,7 +766,7 @@ subgraph "\`**Two**\`"
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -782,7 +782,7 @@ flowchart-elk LR
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
@ -798,7 +798,7 @@ flowchart-elk LR
|
||||
b --> d(The dog in the hog)
|
||||
c --> d
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
@ -808,7 +808,7 @@ flowchart-elk LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
@ -821,7 +821,7 @@ Word!
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
@ -838,7 +838,7 @@ subgraph "\`**Two**\`"
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Flowchart v2', () => {
|
||||
it('1: should render a simple flowchart', () => {
|
||||
@ -671,7 +671,7 @@ title: Simple flowchart
|
||||
flowchart TD
|
||||
A --> B
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 10 } }
|
||||
);
|
||||
});
|
||||
it('3192: It should be possieble to render flowcharts with invisible edges', () => {
|
||||
@ -682,7 +682,7 @@ title: Simple flowchart with invisible edges
|
||||
flowchart TD
|
||||
A ~~~ B
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 10 } }
|
||||
);
|
||||
});
|
||||
it('4023: Should render html labels with images and-or text correctly', () => {
|
||||
@ -695,6 +695,15 @@ A ~~~ B
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('4439: Should render the graph even if some images are missing', () => {
|
||||
imgSnapshotTest(
|
||||
`flowchart TD
|
||||
B[<img>]
|
||||
B-->C[<img>]`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
describe('Markdown strings flowchart (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
@ -707,7 +716,7 @@ flowchart LR
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
@ -723,7 +732,7 @@ flowchart LR
|
||||
b --> d(The dog in the hog)
|
||||
c --> d
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
@ -733,7 +742,7 @@ flowchart LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
@ -746,7 +755,7 @@ Word!
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
@ -763,7 +772,7 @@ subgraph "\`**Two**\`"
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -779,7 +788,7 @@ flowchart LR
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
classDef someclass fill:#f96
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('With formatting in a node', () => {
|
||||
@ -795,7 +804,7 @@ flowchart LR
|
||||
b --> d(The dog in the hog)
|
||||
c --> d
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('New line in node and formatted edge label', () => {
|
||||
@ -805,7 +814,7 @@ flowchart LR
|
||||
b("\`The dog in **the** hog.(1)
|
||||
NL\`") --"\`1o **bold**\`"--> c
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Wrapping long text with a new line', () => {
|
||||
@ -818,7 +827,7 @@ Word!
|
||||
Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
it('Sub graphs and markdown strings', () => {
|
||||
@ -835,7 +844,7 @@ subgraph "\`**Two**\`"
|
||||
end
|
||||
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
{ flowchart: { titleTopMargin: 0 } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Graph', () => {
|
||||
it('1: should render a simple flowchart no htmlLabels', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Gantt diagram', () => {
|
||||
beforeEach(() => {
|
||||
@ -414,6 +414,28 @@ describe('Gantt diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram with tick is 1 week, with the day starting on monday', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %m-%d
|
||||
tickInterval 1week
|
||||
weekday monday
|
||||
excludes weekends
|
||||
|
||||
section Section
|
||||
A task : a1, 2022-10-01, 30d
|
||||
Another task : after a1, 20d
|
||||
section Another
|
||||
Task in sec : 2022-10-20, 12d
|
||||
another task : 24d
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram with tick is 1 month', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Git Graph diagram', () => {
|
||||
it('1: should render a simple gitgraph with commit on main branch', () => {
|
||||
@ -333,4 +333,372 @@ gitGraph
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('15: should render a simple gitgraph with commit on main branch | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "1"
|
||||
commit id: "2"
|
||||
commit id: "3"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('16: should render a simple gitgraph with commit on main branch with Id | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "One"
|
||||
commit id: "Two"
|
||||
commit id: "Three"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('17: should render a simple gitgraph with different commitTypes on main branch | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "Normal Commit"
|
||||
commit id: "Reverse Commit" type: REVERSE
|
||||
commit id: "Hightlight Commit" type: HIGHLIGHT
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('18: should render a simple gitgraph with tags commitTypes on main branch | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "Normal Commit with tag" tag: "v1.0.0"
|
||||
commit id: "Reverse Commit with tag" type: REVERSE tag: "RC_1"
|
||||
commit id: "Hightlight Commit" type: HIGHLIGHT tag: "8.8.4"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('19: should render a simple gitgraph with two branches | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "1"
|
||||
commit id: "2"
|
||||
branch develop
|
||||
checkout develop
|
||||
commit id: "3"
|
||||
commit id: "4"
|
||||
checkout main
|
||||
commit id: "5"
|
||||
commit id: "6"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('20: should render a simple gitgraph with two branches and merge commit | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "1"
|
||||
commit id: "2"
|
||||
branch develop
|
||||
checkout develop
|
||||
commit id: "3"
|
||||
commit id: "4"
|
||||
checkout main
|
||||
merge develop
|
||||
commit id: "5"
|
||||
commit id: "6"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('21: should render a simple gitgraph with three branches and tagged merge commit | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "1"
|
||||
commit id: "2"
|
||||
branch nice_feature
|
||||
checkout nice_feature
|
||||
commit id: "3"
|
||||
checkout main
|
||||
commit id: "4"
|
||||
checkout nice_feature
|
||||
branch very_nice_feature
|
||||
checkout very_nice_feature
|
||||
commit id: "5"
|
||||
checkout main
|
||||
commit id: "6"
|
||||
checkout nice_feature
|
||||
commit id: "7"
|
||||
checkout main
|
||||
merge nice_feature id: "12345" tag: "my merge commit"
|
||||
checkout very_nice_feature
|
||||
commit id: "8"
|
||||
checkout main
|
||||
commit id: "9"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('22: should render a simple gitgraph with more than 8 branchs & overriding variables | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
|
||||
'gitBranchLabel0': '#ffffff',
|
||||
'gitBranchLabel1': '#ffffff',
|
||||
'gitBranchLabel2': '#ffffff',
|
||||
'gitBranchLabel3': '#ffffff',
|
||||
'gitBranchLabel4': '#ffffff',
|
||||
'gitBranchLabel5': '#ffffff',
|
||||
'gitBranchLabel6': '#ffffff',
|
||||
'gitBranchLabel7': '#ffffff',
|
||||
} } }%%
|
||||
gitGraph TB:
|
||||
checkout main
|
||||
branch branch1
|
||||
branch branch2
|
||||
branch branch3
|
||||
branch branch4
|
||||
branch branch5
|
||||
branch branch6
|
||||
branch branch7
|
||||
branch branch8
|
||||
branch branch9
|
||||
checkout branch1
|
||||
commit id: "1"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('23: should render a simple gitgraph with rotated labels | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'gitGraph': {
|
||||
'rotateCommitLabel': true
|
||||
} } }%%
|
||||
gitGraph TB:
|
||||
commit id: "75f7219e83b321cd3fdde7dcf83bc7c1000a6828"
|
||||
commit id: "0db4784daf82736dec4569e0dc92980d328c1f2e"
|
||||
commit id: "7067e9973f9eaa6cd4a4b723c506d1eab598e83e"
|
||||
commit id: "66972321ad6c199013b5b31f03b3a86fa3f9817d"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('24: should render a simple gitgraph with horizontal labels | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: { 'logLevel': 'debug', 'theme': 'default' , 'gitGraph': {
|
||||
'rotateCommitLabel': false
|
||||
} } }%%
|
||||
gitGraph TB:
|
||||
commit id: "Alpha"
|
||||
commit id: "Beta"
|
||||
commit id: "Gamma"
|
||||
commit id: "Delta"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('25: should render a simple gitgraph with cherry pick commit | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph TB:
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('26: should render a gitgraph with cherry pick commit with custom tag | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph TB:
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A" tag: "snapshot"
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('27: should render a gitgraph with cherry pick commit with no tag | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph TB:
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A" tag: ""
|
||||
commit id:"THREE"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('28: should render a simple gitgraph with two cherry pick commit | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph TB:
|
||||
commit id: "ZERO"
|
||||
branch develop
|
||||
commit id:"A"
|
||||
checkout main
|
||||
commit id:"ONE"
|
||||
checkout develop
|
||||
commit id:"B"
|
||||
branch featureA
|
||||
commit id:"FIX"
|
||||
commit id: "FIX-2"
|
||||
checkout main
|
||||
commit id:"TWO"
|
||||
cherry-pick id:"A"
|
||||
commit id:"THREE"
|
||||
cherry-pick id:"FIX"
|
||||
checkout develop
|
||||
commit id:"C"
|
||||
merge featureA
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('29: should render commits for more than 8 branches | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gitGraph TB:
|
||||
checkout main
|
||||
%% Make sure to manually set the ID of all commits, for consistent visual tests
|
||||
commit id: "1-abcdefg"
|
||||
checkout main
|
||||
branch branch1
|
||||
commit id: "2-abcdefg"
|
||||
checkout main
|
||||
merge branch1
|
||||
branch branch2
|
||||
commit id: "3-abcdefg"
|
||||
checkout main
|
||||
merge branch2
|
||||
branch branch3
|
||||
commit id: "4-abcdefg"
|
||||
checkout main
|
||||
merge branch3
|
||||
branch branch4
|
||||
commit id: "5-abcdefg"
|
||||
checkout main
|
||||
merge branch4
|
||||
branch branch5
|
||||
commit id: "6-abcdefg"
|
||||
checkout main
|
||||
merge branch5
|
||||
branch branch6
|
||||
commit id: "7-abcdefg"
|
||||
checkout main
|
||||
merge branch6
|
||||
branch branch7
|
||||
commit id: "8-abcdefg"
|
||||
checkout main
|
||||
merge branch7
|
||||
branch branch8
|
||||
commit id: "9-abcdefg"
|
||||
checkout main
|
||||
merge branch8
|
||||
branch branch9
|
||||
commit id: "10-abcdefg"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('30: should render a simple gitgraph with three branches,custom merge commit id,tag,type | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id: "1"
|
||||
commit id: "2"
|
||||
branch nice_feature
|
||||
checkout nice_feature
|
||||
commit id: "3"
|
||||
checkout main
|
||||
commit id: "4"
|
||||
checkout nice_feature
|
||||
branch very_nice_feature
|
||||
checkout very_nice_feature
|
||||
commit id: "5"
|
||||
checkout main
|
||||
commit id: "6"
|
||||
checkout nice_feature
|
||||
commit id: "7"
|
||||
checkout main
|
||||
merge nice_feature id: "customID" tag: "customTag" type: REVERSE
|
||||
checkout very_nice_feature
|
||||
commit id: "8"
|
||||
checkout main
|
||||
commit id: "9"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('31: should render a simple gitgraph with a title | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
title: simple gitGraph
|
||||
---
|
||||
gitGraph TB:
|
||||
commit id: "1-abcdefg"
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('32: should render a simple gitgraph overlapping commits | Vertical Branch', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph TB:
|
||||
commit id:"s1"
|
||||
commit id:"s2"
|
||||
branch branch1
|
||||
commit id:"s3"
|
||||
commit id:"s4"
|
||||
checkout main
|
||||
commit id:"s5"
|
||||
checkout branch1
|
||||
commit id:"s6"
|
||||
commit id:"s7"
|
||||
merge main
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('33: should render a simple gitgraph overlapping commits', () => {
|
||||
imgSnapshotTest(
|
||||
`gitGraph
|
||||
commit id:"s1"
|
||||
commit id:"s2"
|
||||
branch branch1
|
||||
commit id:"s3"
|
||||
commit id:"s4"
|
||||
checkout main
|
||||
commit id:"s5"
|
||||
checkout branch1
|
||||
commit id:"s6"
|
||||
commit id:"s7"
|
||||
merge main
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('info diagram', () => {
|
||||
it('should handle an info definition', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('User journey diagram', () => {
|
||||
it('Simple test', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
/**
|
||||
* Check whether the SVG Element has a Mindmap root
|
||||
@ -242,8 +242,7 @@ mindmap
|
||||
a second line 😎\`]
|
||||
id2[\`The dog in **the** hog... a *very long text* about it
|
||||
Word!\`]
|
||||
`,
|
||||
{ titleTopMargin: 0 }
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Pie Chart', () => {
|
||||
it('should render a simple pie diagram', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Quadrant Chart', () => {
|
||||
it('should render if only chart type is provided', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Requirement diagram', () => {
|
||||
it('sample', () => {
|
||||
|
144
cypress/integration/rendering/sankey.spec.ts
Normal file
144
cypress/integration/rendering/sankey.spec.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Sankey Diagram', () => {
|
||||
it('should render a simple example', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sankey-beta
|
||||
|
||||
sourceNode,targetNode,10
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
describe('when given a linkColor', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.wrap(
|
||||
`sankey-beta
|
||||
a,b,10
|
||||
`
|
||||
).as('graph');
|
||||
});
|
||||
|
||||
it('links should use hex color', function () {
|
||||
renderGraph(this.graph, { sankey: { linkColor: '#636465' } });
|
||||
|
||||
cy.get('.link path').should((link) => {
|
||||
expect(link.attr('stroke')).to.equal('#636465');
|
||||
});
|
||||
});
|
||||
|
||||
it('links should be the same color as source node', function () {
|
||||
renderGraph(this.graph, { sankey: { linkColor: 'source' } });
|
||||
|
||||
cy.get('.link path').then((link) => {
|
||||
cy.get('.node[id="node-1"] rect').should((node) =>
|
||||
expect(link.attr('stroke')).to.equal(node.attr('fill'))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('links should be the same color as target node', function () {
|
||||
renderGraph(this.graph, { sankey: { linkColor: 'target' } });
|
||||
|
||||
cy.get('.link path').then((link) => {
|
||||
cy.get('.node[id="node-2"] rect').should((node) =>
|
||||
expect(link.attr('stroke')).to.equal(node.attr('fill'))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('links must be gradient', function () {
|
||||
renderGraph(this.graph, { sankey: { linkColor: 'gradient' } });
|
||||
|
||||
cy.get('.link path').should((link) => {
|
||||
expect(link.attr('stroke')).to.equal('url(#linearGradient-3)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when given a nodeAlignment', function () {
|
||||
this.beforeAll(() => {
|
||||
cy.wrap(
|
||||
`
|
||||
sankey-beta
|
||||
|
||||
a,b,8
|
||||
b,c,8
|
||||
c,d,8
|
||||
d,e,8
|
||||
|
||||
x,c,4
|
||||
c,y,4
|
||||
`
|
||||
).as('graph');
|
||||
});
|
||||
|
||||
this.afterEach(() => {
|
||||
cy.get('.node[id="node-1"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('0');
|
||||
});
|
||||
cy.get('.node[id="node-2"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('100');
|
||||
});
|
||||
cy.get('.node[id="node-3"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('200');
|
||||
});
|
||||
cy.get('.node[id="node-4"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('300');
|
||||
});
|
||||
cy.get('.node[id="node-5"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('400');
|
||||
});
|
||||
});
|
||||
|
||||
it('should justify nodes', function () {
|
||||
renderGraph(this.graph, {
|
||||
sankey: { nodeAlignment: 'justify', width: 410, useMaxWidth: false },
|
||||
});
|
||||
cy.get('.node[id="node-6"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('0');
|
||||
});
|
||||
cy.get('.node[id="node-7"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('400');
|
||||
});
|
||||
});
|
||||
|
||||
it('should align nodes left', function () {
|
||||
renderGraph(this.graph, {
|
||||
sankey: { nodeAlignment: 'left', width: 410, useMaxWidth: false },
|
||||
});
|
||||
cy.get('.node[id="node-6"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('0');
|
||||
});
|
||||
cy.get('.node[id="node-7"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('300');
|
||||
});
|
||||
});
|
||||
|
||||
it('should align nodes right', function () {
|
||||
renderGraph(this.graph, {
|
||||
sankey: { nodeAlignment: 'right', width: 410, useMaxWidth: false },
|
||||
});
|
||||
cy.get('.node[id="node-6"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('100');
|
||||
});
|
||||
cy.get('.node[id="node-7"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('400');
|
||||
});
|
||||
});
|
||||
|
||||
it('should center nodes', function () {
|
||||
renderGraph(this.graph, {
|
||||
sankey: { nodeAlignment: 'center', width: 410, useMaxWidth: false },
|
||||
});
|
||||
cy.get('.node[id="node-6"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('100');
|
||||
});
|
||||
cy.get('.node[id="node-7"]').should((node) => {
|
||||
expect(node.attr('x')).to.equal('300');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
context('Sequence diagram', () => {
|
||||
it('should render a sequence diagram with boxes', () => {
|
||||
@ -156,6 +156,81 @@ context('Sequence diagram', () => {
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should render a sequence diagram with basic actor creation and destruction', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice ->> Bob: Hello Bob, how are you ?
|
||||
Bob ->> Alice: Fine, thank you. And you?
|
||||
create participant Polo
|
||||
Alice ->> Polo: Hi Polo!
|
||||
create actor Ola1 as Ola
|
||||
Polo ->> Ola1: Hiii
|
||||
Ola1 ->> Alice: Hi too
|
||||
destroy Ola1
|
||||
Alice --x Ola1: Bye!
|
||||
Alice ->> Bob: And now?
|
||||
create participant Ola2 as Ola
|
||||
Alice ->> Ola2: Hello again
|
||||
destroy Alice
|
||||
Alice --x Ola2: Bye for me!
|
||||
destroy Bob
|
||||
Ola2 --> Bob: The end
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should render a sequence diagram with actor creation and destruction coupled with backgrounds, loops and notes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
accTitle: test the accTitle
|
||||
accDescr: Test a description
|
||||
|
||||
participant Alice
|
||||
participant Bob
|
||||
autonumber 10 10
|
||||
rect rgb(200, 220, 100)
|
||||
rect rgb(200, 255, 200)
|
||||
|
||||
Alice ->> Bob: Hello Bob, how are you?
|
||||
create participant John as John<br />Second Line
|
||||
Bob-->>John: How about you John?
|
||||
end
|
||||
|
||||
Bob--x Alice: I am good thanks!
|
||||
Bob-x John: I am good thanks!
|
||||
Note right of John: John thinks a long<br />long time, so long<br />that the text does<br />not fit on a row.
|
||||
|
||||
Bob-->Alice: Checking with John...
|
||||
Note over John:wrap: John looks like he's still thinking, so Bob prods him a bit.
|
||||
Bob-x John: Hey John - we're still waiting to know<br />how you're doing
|
||||
Note over John:nowrap: John's trying hard not to break his train of thought.
|
||||
destroy John
|
||||
Bob-x John: John! Cmon!
|
||||
Note over John: After a few more moments, John<br />finally snaps out of it.
|
||||
end
|
||||
|
||||
autonumber off
|
||||
alt either this
|
||||
create actor Lola
|
||||
Alice->>+Lola: Yes
|
||||
Lola-->>-Alice: OK
|
||||
else or this
|
||||
autonumber
|
||||
Alice->>Lola: No
|
||||
else or this will happen
|
||||
Alice->Lola: Maybe
|
||||
end
|
||||
autonumber 200
|
||||
par this happens in parallel
|
||||
destroy Bob
|
||||
Alice -->> Bob: Parallel message 1
|
||||
and
|
||||
Alice -->> Lola: Parallel message 2
|
||||
end
|
||||
`
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
it('should render different note fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('State diagram', () => {
|
||||
it('v2 should render a simple info', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('State diagram', () => {
|
||||
it('should render a simple state diagrams', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('themeCSS balancing, it', () => {
|
||||
it('should not allow unbalanced CSS definitions', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Timeline diagram', () => {
|
||||
it('1: should render a simple timeline with no specific sections', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
import { imgSnapshotTest } from '../../helpers/util.ts';
|
||||
|
||||
describe('Zen UML', () => {
|
||||
it('Basic Zen UML diagram', () => {
|
||||
|
@ -58,6 +58,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
---
|
||||
title: Simple flowchart with invisible edges
|
||||
---
|
||||
flowchart TD
|
||||
A ~~~ B
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
@ -73,7 +81,7 @@ flowchart RL
|
||||
a1 -- l2 --> a2
|
||||
end
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
flowchart RL
|
||||
subgraph "`one`"
|
||||
a1 -- l1 --> a2
|
||||
@ -98,11 +106,11 @@ flowchart LR
|
||||
way`"]
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
classDiagram-v2
|
||||
note "I love this diagram!\nDo you love it?"
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
stateDiagram-v2
|
||||
State1: The state with a note with minus - and plus + in it
|
||||
note left of State1
|
||||
@ -147,7 +155,7 @@ mindmap
|
||||
शान्तिः سلام 和平 `"]
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid">
|
||||
<pre id="diagram" class="mermaid2">
|
||||
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
|
||||
flowchart TB
|
||||
%% I could not figure out how to use double quotes in labels in Mermaid
|
||||
@ -399,21 +407,31 @@ mindmap
|
||||
mermaid.parseError = function (err, hash) {
|
||||
// console.error('Mermaid error: ', err);
|
||||
};
|
||||
// mermaid.initialize({
|
||||
// // theme: 'forest',
|
||||
// startOnLoad: true,
|
||||
// logLevel: 0,
|
||||
// flowchart: {
|
||||
// // defaultRenderer: 'elk',
|
||||
// useMaxWidth: false,
|
||||
// // htmlLabels: false,
|
||||
// htmlLabels: true,
|
||||
// },
|
||||
// // htmlLabels: false,
|
||||
// gantt: {
|
||||
// useMaxWidth: false,
|
||||
// },
|
||||
// useMaxWidth: false,
|
||||
// });
|
||||
mermaid.initialize({
|
||||
// theme: 'forest',
|
||||
startOnLoad: true,
|
||||
logLevel: 0,
|
||||
flowchart: {
|
||||
// defaultRenderer: 'elk',
|
||||
useMaxWidth: false,
|
||||
// htmlLabels: false,
|
||||
htmlLabels: true,
|
||||
flowchart: { titleTopMargin: 10 },
|
||||
fontFamily: 'courier',
|
||||
sequence: {
|
||||
actorFontFamily: 'courier',
|
||||
noteFontFamily: 'courier',
|
||||
messageFontFamily: 'courier',
|
||||
},
|
||||
// htmlLabels: false,
|
||||
gantt: {
|
||||
useMaxWidth: false,
|
||||
},
|
||||
useMaxWidth: false,
|
||||
fontSize: 16,
|
||||
});
|
||||
function callback() {
|
||||
alert('It worked');
|
||||
|
@ -2,7 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"types": ["cypress", "node"]
|
||||
"types": ["cypress", "node"],
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
|
@ -75,6 +75,9 @@
|
||||
<li>
|
||||
<h2><a href="./zenuml.html">ZenUML</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a href="./sankey.html">Sankey</a></h2>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
108
demos/sankey.html
Normal file
108
demos/sankey.html
Normal file
@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>States Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Sankey diagram demos</h1>
|
||||
<h2>Energy flow</h2>
|
||||
<pre class="mermaid">
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
Bio-conversion,Losses,26.862
|
||||
Bio-conversion,Solid,280.322
|
||||
Bio-conversion,Gas,81.144
|
||||
Biofuel imports,Liquid,35
|
||||
Biomass imports,Solid,35
|
||||
Coal imports,Coal,11.606
|
||||
Coal reserves,Coal,63.965
|
||||
Coal,Solid,75.571
|
||||
District heating,Industry,10.639
|
||||
District heating,Heating and cooling - commercial,22.505
|
||||
District heating,Heating and cooling - homes,46.184
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
Electricity grid,Industry,342.165
|
||||
Electricity grid,Road transport,37.797
|
||||
Electricity grid,Agriculture,4.412
|
||||
Electricity grid,Heating and cooling - commercial,40.858
|
||||
Electricity grid,Losses,56.691
|
||||
Electricity grid,Rail transport,7.863
|
||||
Electricity grid,Lighting & appliances - commercial,90.008
|
||||
Electricity grid,Lighting & appliances - homes,93.494
|
||||
Gas imports,Ngas,40.719
|
||||
Gas reserves,Ngas,82.233
|
||||
Gas,Heating and cooling - commercial,0.129
|
||||
Gas,Losses,1.401
|
||||
Gas,Thermal generation,151.891
|
||||
Gas,Agriculture,2.096
|
||||
Gas,Industry,48.58
|
||||
Geothermal,Electricity grid,7.013
|
||||
H2 conversion,H2,20.897
|
||||
H2 conversion,Losses,6.242
|
||||
H2,Road transport,20.897
|
||||
Hydro,Electricity grid,6.995
|
||||
Liquid,Industry,121.066
|
||||
Liquid,International shipping,128.69
|
||||
Liquid,Road transport,135.835
|
||||
Liquid,Domestic aviation,14.458
|
||||
Liquid,International aviation,206.267
|
||||
Liquid,Agriculture,3.64
|
||||
Liquid,National navigation,33.218
|
||||
Liquid,Rail transport,4.413
|
||||
Marine algae,Bio-conversion,4.375
|
||||
Ngas,Gas,122.952
|
||||
Nuclear,Thermal generation,839.978
|
||||
Oil imports,Oil,504.287
|
||||
Oil reserves,Oil,107.703
|
||||
Oil,Liquid,611.99
|
||||
Other waste,Solid,56.587
|
||||
Other waste,Bio-conversion,77.81
|
||||
Pumped heat,Heating and cooling - homes,193.026
|
||||
Pumped heat,Heating and cooling - commercial,70.672
|
||||
Solar PV,Electricity grid,59.901
|
||||
Solar Thermal,Heating and cooling - homes,19.263
|
||||
Solar,Solar Thermal,19.263
|
||||
Solar,Solar PV,59.901
|
||||
Solid,Agriculture,0.882
|
||||
Solid,Thermal generation,400.12
|
||||
Solid,Industry,46.477
|
||||
Thermal generation,Electricity grid,525.531
|
||||
Thermal generation,Losses,787.129
|
||||
Thermal generation,District heating,79.329
|
||||
Tidal,Electricity grid,9.452
|
||||
UK land based bioenergy,Bio-conversion,182.01
|
||||
Wave,Electricity grid,19.013
|
||||
Wind,Electricity grid,289.366
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'default',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
sankey: {
|
||||
title: 'Hey, this is Sankey-Beta',
|
||||
width: 1200,
|
||||
height: 600,
|
||||
linkColor: 'gradient',
|
||||
nodeAlignment: 'justify',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,9 +1,36 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
mermaid:
|
||||
image: node:20.3.1-alpine3.18
|
||||
image: node:18.17.0-alpine3.18
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
mem_limit: '2G'
|
||||
environment:
|
||||
- NODE_OPTIONS=--max_old_space_size=2048
|
||||
volumes:
|
||||
- ./:/mermaid
|
||||
- root_cache:/root/.cache
|
||||
- root_local:/root/.local
|
||||
- root_npm:/root/.npm
|
||||
ports:
|
||||
- 9000:9000
|
||||
- 3333:3333
|
||||
cypress:
|
||||
image: cypress/included:12.17.2
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
mem_limit: '2G'
|
||||
entrypoint: cypress
|
||||
environment:
|
||||
- DISPLAY
|
||||
volumes:
|
||||
- ./:/mermaid
|
||||
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||
network_mode: host
|
||||
|
||||
volumes:
|
||||
root_cache:
|
||||
root_local:
|
||||
root_npm:
|
||||
|
@ -223,6 +223,9 @@ If the users have no way to know that things have changed, then you haven't real
|
||||
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
|
||||
|
||||
The documentation has to be updated to users know that things have changed and added!
|
||||
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
|
||||
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
|
||||
|
||||
We know it can sometimes be hard to code _and_ write user documentation.
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293)
|
||||
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
||||
|
||||
---
|
||||
|
||||
@ -22,35 +22,12 @@
|
||||
|
||||
• `Const` **default**: `Partial`<`MermaidConfig`>
|
||||
|
||||
**Configuration methods in Mermaid version 8.6.0 have been updated, to learn more\[[click
|
||||
here](8.6.0_docs.md)].**
|
||||
Default mermaid configuration options.
|
||||
|
||||
## **What follows are config instructions for older versions**
|
||||
|
||||
These are the default options which can be overridden with the initialization call like so:
|
||||
|
||||
**Example 1:**
|
||||
|
||||
```js
|
||||
mermaid.initialize({ flowchart: { htmlLabels: false } });
|
||||
```
|
||||
|
||||
**Example 2:**
|
||||
|
||||
```html
|
||||
<script>
|
||||
const config = {
|
||||
startOnLoad: true,
|
||||
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
```
|
||||
|
||||
A summary of all options and their defaults is found [here](#mermaidapi-configuration-defaults).
|
||||
A description of each option follows below.
|
||||
Please see the Mermaid config JSON Schema for the default JSON values.
|
||||
Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||
`undefined` (explicitly set so that `configKeys` finds them).
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L33)
|
||||
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
|
||||
|
@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:663](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L663)
|
||||
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
||||
|
||||
## Functions
|
||||
|
||||
|
@ -73,9 +73,9 @@ To make a custom theme, modify `themeVariables` via `init`.
|
||||
|
||||
You will need to use the [base](#available-themes) theme as it is the only modifiable theme.
|
||||
|
||||
| Parameter | Description | Type | Properties |
|
||||
| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- |
|
||||
| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) |
|
||||
| Parameter | Description | Type | Properties |
|
||||
| -------------- | ------------------------------------ | ------ | ----------------------------------------------------------------------------------- |
|
||||
| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) |
|
||||
|
||||
Example of modifying `themeVariables` using the `init` directive:
|
||||
|
||||
|
@ -228,7 +228,7 @@ mermaid fully supports webpack. Here is a [working demo](https://github.com/merm
|
||||
|
||||
The main idea of the API is to be able to call a render function with the graph definition as a string. The render function will render the graph and call a callback with the resulting SVG code. With this approach it is up to the site creator to fetch the graph definition from the site (perhaps from a textarea), render it and place the graph somewhere in the site.
|
||||
|
||||
The example below show an outline of how this could be used. The example just logs the resulting SVG to the JavaScript console.
|
||||
The example below shows an example of how this could be used. The example just logs the resulting SVG to the JavaScript console.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
|
@ -187,6 +187,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [mdbook](https://rust-lang.github.io/mdBook/index.html)
|
||||
- [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid)
|
||||
- [Quarto](https://quarto.org/)
|
||||
- [Typora](https://typora.io/) ([Native support](https://support.typora.io/Draw-Diagrams-With-Markdown/#mermaid))
|
||||
|
||||
## Browser Extensions
|
||||
|
||||
|
@ -387,7 +387,9 @@ The above command generates files into the `dist` folder and publishes them to \
|
||||
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||
- [HTTP Server](https://github.com/TomWright/mermaid-server)
|
||||
|
||||
## Contributors [![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
## Contributors
|
||||
|
||||
[![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
|
||||
|
||||
|
@ -123,10 +123,10 @@ The layout does not use a fully automated layout algorithm. The position of shap
|
||||
The number of shapes per row and the number of boundaries can be adjusted using UpdateLayoutConfig.
|
||||
|
||||
- Layout
|
||||
- - Lay_U, Lay_Up
|
||||
- - Lay_D, Lay_Down
|
||||
- - Lay_L, Lay_Left
|
||||
- - Lay_R, Lay_Right
|
||||
- Lay_U, Lay_Up
|
||||
- Lay_D, Lay_Down
|
||||
- Lay_L, Lay_Left
|
||||
- Lay_R, Lay_Right
|
||||
|
||||
The following unfinished features are not supported in the short term.
|
||||
|
||||
@ -140,111 +140,70 @@ The following unfinished features are not supported in the short term.
|
||||
|
||||
- [x] System Context
|
||||
|
||||
- - [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] Person_Ext
|
||||
|
||||
- - [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] SystemDb
|
||||
|
||||
- - [x] SystemQueue
|
||||
|
||||
- - [x] System_Ext
|
||||
|
||||
- - [x] SystemDb_Ext
|
||||
|
||||
- - [x] SystemQueue_Ext
|
||||
|
||||
- - [x] Boundary(alias, label, ?type, ?tags, $link)
|
||||
|
||||
- - [x] Enterprise_Boundary(alias, label, ?tags, $link)
|
||||
|
||||
- - [x] System_Boundary
|
||||
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Person_Ext
|
||||
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] SystemDb
|
||||
- [x] SystemQueue
|
||||
- [x] System_Ext
|
||||
- [x] SystemDb_Ext
|
||||
- [x] SystemQueue_Ext
|
||||
- [x] Boundary(alias, label, ?type, ?tags, $link)
|
||||
- [x] Enterprise_Boundary(alias, label, ?tags, $link)
|
||||
- [x] System_Boundary
|
||||
|
||||
- [x] Container diagram
|
||||
|
||||
- - [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] ContainerDb
|
||||
|
||||
- - [x] ContainerQueue
|
||||
|
||||
- - [x] Container_Ext
|
||||
|
||||
- - [x] ContainerDb_Ext
|
||||
|
||||
- - [x] ContainerQueue_Ext
|
||||
|
||||
- - [x] Container_Boundary(alias, label, ?tags, $link)
|
||||
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ContainerDb
|
||||
- [x] ContainerQueue
|
||||
- [x] Container_Ext
|
||||
- [x] ContainerDb_Ext
|
||||
- [x] ContainerQueue_Ext
|
||||
- [x] Container_Boundary(alias, label, ?tags, $link)
|
||||
|
||||
- [x] Component diagram
|
||||
|
||||
- - [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] ComponentDb
|
||||
|
||||
- - [x] ComponentQueue
|
||||
|
||||
- - [x] Component_Ext
|
||||
|
||||
- - [x] ComponentDb_Ext
|
||||
|
||||
- - [x] ComponentQueue_Ext
|
||||
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ComponentDb
|
||||
- [x] ComponentQueue
|
||||
- [x] Component_Ext
|
||||
- [x] ComponentDb_Ext
|
||||
- [x] ComponentQueue_Ext
|
||||
|
||||
- [x] Dynamic diagram
|
||||
|
||||
- - [x] RelIndex(index, from, to, label, ?tags, $link)
|
||||
- [x] RelIndex(index, from, to, label, ?tags, $link)
|
||||
|
||||
- [x] Deployment diagram
|
||||
|
||||
- - [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
|
||||
|
||||
- - [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
|
||||
|
||||
- - [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
|
||||
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
|
||||
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
|
||||
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
|
||||
|
||||
- [x] Relationship Types
|
||||
|
||||
- - [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
|
||||
- - [x] BiRel (bidirectional relationship)
|
||||
|
||||
- - [x] Rel_U, Rel_Up
|
||||
|
||||
- - [x] Rel_D, Rel_Down
|
||||
|
||||
- - [x] Rel_L, Rel_Left
|
||||
|
||||
- - [x] Rel_R, Rel_Right
|
||||
|
||||
- - [x] Rel_Back
|
||||
|
||||
- - [x] RelIndex \* Compatible with C4-Plantuml syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written.
|
||||
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] BiRel (bidirectional relationship)
|
||||
- [x] Rel_U, Rel_Up
|
||||
- [x] Rel_D, Rel_Down
|
||||
- [x] Rel_L, Rel_Left
|
||||
- [x] Rel_R, Rel_Right
|
||||
- [x] Rel_Back
|
||||
- [x] RelIndex \* Compatible with C4-Plantuml syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written.
|
||||
|
||||
- [ ] Custom tags/stereotypes support and skin param updates
|
||||
|
||||
- - [ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend.
|
||||
|
||||
- - [ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend.
|
||||
|
||||
- - [x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry.
|
||||
|
||||
- - [x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text.
|
||||
|
||||
- - [ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument.
|
||||
|
||||
- - [ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument.
|
||||
|
||||
- - [ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument.
|
||||
|
||||
- - [ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument.
|
||||
|
||||
- - [ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument.
|
||||
|
||||
- - [x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2).
|
||||
- [ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend.
|
||||
- [ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend.
|
||||
- [x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry.
|
||||
- [x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text.
|
||||
- [ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument.
|
||||
- [ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument.
|
||||
- [ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument.
|
||||
- [ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument.
|
||||
- [ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument.
|
||||
- [x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2).
|
||||
|
||||
There are two ways to assign parameters with question marks. One uses the non-named parameter assignment method in the order of the parameters, and the other uses the named parameter assignment method, where the name must start with a $ symbol.
|
||||
|
||||
|
@ -196,7 +196,7 @@ erDiagram
|
||||
}
|
||||
```
|
||||
|
||||
The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||
|
||||
#### Attribute Keys and Comments
|
||||
|
||||
|
@ -991,6 +991,24 @@ flowchart LR
|
||||
classDef someclass fill:#f96
|
||||
```
|
||||
|
||||
This form can be used when declaring multiple links between nodes:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
A:::foo & B:::bar --> C:::foobar
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A:::foo & B:::bar --> C:::foobar
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
### Css classes
|
||||
|
||||
It is also possible to predefine classes in css styles that can be applied from the graph definition as in the example
|
||||
|
@ -257,6 +257,22 @@ The pattern is:
|
||||
|
||||
More info in: <https://github.com/d3/d3-time#interval_every>
|
||||
|
||||
Week-based `tickInterval`s start the week on sunday by default. If you wish to specify another weekday on which the `tickInterval` should start, use the `weekday` option:
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
tickInterval 1week
|
||||
weekday monday
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
tickInterval 1week
|
||||
weekday monday
|
||||
```
|
||||
|
||||
Support: v10.3.0+
|
||||
|
||||
## Output in compact mode
|
||||
|
||||
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceeding YAML settings.
|
||||
|
@ -825,6 +825,82 @@ NOTE: Because we have overridden the `mainBranchOrder` to `2`, the `main` branch
|
||||
|
||||
Here, we have changed the default main branch name to `MetroLine1`.
|
||||
|
||||
## Orientation (v10.3.0+)
|
||||
|
||||
In Mermaid, the default orientation is Left to Right. The branches are lined vertically.
|
||||
|
||||
Usage example:
|
||||
|
||||
```mermaid-example
|
||||
gitGraph
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
commit
|
||||
commit
|
||||
commit
|
||||
checkout main
|
||||
commit
|
||||
commit
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gitGraph
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
commit
|
||||
commit
|
||||
commit
|
||||
checkout main
|
||||
commit
|
||||
commit
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
```
|
||||
|
||||
Sometimes we may want to change the orientation. Currently, Mermaid supports two orientations: **Left to Right**(default) and **Top to Bottom**.
|
||||
|
||||
In order to change the orientation from top to bottom i.e. branches lined horizontally, you need to add `TB` along with `gitGraph`.
|
||||
|
||||
Usage example:
|
||||
|
||||
```mermaid-example
|
||||
gitGraph TB:
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
commit
|
||||
commit
|
||||
commit
|
||||
checkout main
|
||||
commit
|
||||
commit
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gitGraph TB:
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
commit
|
||||
commit
|
||||
commit
|
||||
checkout main
|
||||
commit
|
||||
commit
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
```
|
||||
|
||||
## Themes
|
||||
|
||||
Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md).
|
||||
|
294
docs/syntax/sankey.md
Normal file
294
docs/syntax/sankey.md
Normal file
@ -0,0 +1,294 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/sankey.md](../../packages/mermaid/src/docs/syntax/sankey.md).
|
||||
|
||||
# Sankey diagram (v10.3.0+)
|
||||
|
||||
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
|
||||
|
||||
::: warning
|
||||
This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
|
||||
:::
|
||||
|
||||
The things being connected are called nodes and the connections are called links.
|
||||
|
||||
## Example
|
||||
|
||||
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
Bio-conversion,Losses,26.862
|
||||
Bio-conversion,Solid,280.322
|
||||
Bio-conversion,Gas,81.144
|
||||
Biofuel imports,Liquid,35
|
||||
Biomass imports,Solid,35
|
||||
Coal imports,Coal,11.606
|
||||
Coal reserves,Coal,63.965
|
||||
Coal,Solid,75.571
|
||||
District heating,Industry,10.639
|
||||
District heating,Heating and cooling - commercial,22.505
|
||||
District heating,Heating and cooling - homes,46.184
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
Electricity grid,Industry,342.165
|
||||
Electricity grid,Road transport,37.797
|
||||
Electricity grid,Agriculture,4.412
|
||||
Electricity grid,Heating and cooling - commercial,40.858
|
||||
Electricity grid,Losses,56.691
|
||||
Electricity grid,Rail transport,7.863
|
||||
Electricity grid,Lighting & appliances - commercial,90.008
|
||||
Electricity grid,Lighting & appliances - homes,93.494
|
||||
Gas imports,Ngas,40.719
|
||||
Gas reserves,Ngas,82.233
|
||||
Gas,Heating and cooling - commercial,0.129
|
||||
Gas,Losses,1.401
|
||||
Gas,Thermal generation,151.891
|
||||
Gas,Agriculture,2.096
|
||||
Gas,Industry,48.58
|
||||
Geothermal,Electricity grid,7.013
|
||||
H2 conversion,H2,20.897
|
||||
H2 conversion,Losses,6.242
|
||||
H2,Road transport,20.897
|
||||
Hydro,Electricity grid,6.995
|
||||
Liquid,Industry,121.066
|
||||
Liquid,International shipping,128.69
|
||||
Liquid,Road transport,135.835
|
||||
Liquid,Domestic aviation,14.458
|
||||
Liquid,International aviation,206.267
|
||||
Liquid,Agriculture,3.64
|
||||
Liquid,National navigation,33.218
|
||||
Liquid,Rail transport,4.413
|
||||
Marine algae,Bio-conversion,4.375
|
||||
Ngas,Gas,122.952
|
||||
Nuclear,Thermal generation,839.978
|
||||
Oil imports,Oil,504.287
|
||||
Oil reserves,Oil,107.703
|
||||
Oil,Liquid,611.99
|
||||
Other waste,Solid,56.587
|
||||
Other waste,Bio-conversion,77.81
|
||||
Pumped heat,Heating and cooling - homes,193.026
|
||||
Pumped heat,Heating and cooling - commercial,70.672
|
||||
Solar PV,Electricity grid,59.901
|
||||
Solar Thermal,Heating and cooling - homes,19.263
|
||||
Solar,Solar Thermal,19.263
|
||||
Solar,Solar PV,59.901
|
||||
Solid,Agriculture,0.882
|
||||
Solid,Thermal generation,400.12
|
||||
Solid,Industry,46.477
|
||||
Thermal generation,Electricity grid,525.531
|
||||
Thermal generation,Losses,787.129
|
||||
Thermal generation,District heating,79.329
|
||||
Tidal,Electricity grid,9.452
|
||||
UK land based bioenergy,Bio-conversion,182.01
|
||||
Wave,Electricity grid,19.013
|
||||
Wind,Electricity grid,289.366
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
Bio-conversion,Losses,26.862
|
||||
Bio-conversion,Solid,280.322
|
||||
Bio-conversion,Gas,81.144
|
||||
Biofuel imports,Liquid,35
|
||||
Biomass imports,Solid,35
|
||||
Coal imports,Coal,11.606
|
||||
Coal reserves,Coal,63.965
|
||||
Coal,Solid,75.571
|
||||
District heating,Industry,10.639
|
||||
District heating,Heating and cooling - commercial,22.505
|
||||
District heating,Heating and cooling - homes,46.184
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
Electricity grid,Industry,342.165
|
||||
Electricity grid,Road transport,37.797
|
||||
Electricity grid,Agriculture,4.412
|
||||
Electricity grid,Heating and cooling - commercial,40.858
|
||||
Electricity grid,Losses,56.691
|
||||
Electricity grid,Rail transport,7.863
|
||||
Electricity grid,Lighting & appliances - commercial,90.008
|
||||
Electricity grid,Lighting & appliances - homes,93.494
|
||||
Gas imports,Ngas,40.719
|
||||
Gas reserves,Ngas,82.233
|
||||
Gas,Heating and cooling - commercial,0.129
|
||||
Gas,Losses,1.401
|
||||
Gas,Thermal generation,151.891
|
||||
Gas,Agriculture,2.096
|
||||
Gas,Industry,48.58
|
||||
Geothermal,Electricity grid,7.013
|
||||
H2 conversion,H2,20.897
|
||||
H2 conversion,Losses,6.242
|
||||
H2,Road transport,20.897
|
||||
Hydro,Electricity grid,6.995
|
||||
Liquid,Industry,121.066
|
||||
Liquid,International shipping,128.69
|
||||
Liquid,Road transport,135.835
|
||||
Liquid,Domestic aviation,14.458
|
||||
Liquid,International aviation,206.267
|
||||
Liquid,Agriculture,3.64
|
||||
Liquid,National navigation,33.218
|
||||
Liquid,Rail transport,4.413
|
||||
Marine algae,Bio-conversion,4.375
|
||||
Ngas,Gas,122.952
|
||||
Nuclear,Thermal generation,839.978
|
||||
Oil imports,Oil,504.287
|
||||
Oil reserves,Oil,107.703
|
||||
Oil,Liquid,611.99
|
||||
Other waste,Solid,56.587
|
||||
Other waste,Bio-conversion,77.81
|
||||
Pumped heat,Heating and cooling - homes,193.026
|
||||
Pumped heat,Heating and cooling - commercial,70.672
|
||||
Solar PV,Electricity grid,59.901
|
||||
Solar Thermal,Heating and cooling - homes,19.263
|
||||
Solar,Solar Thermal,19.263
|
||||
Solar,Solar PV,59.901
|
||||
Solid,Agriculture,0.882
|
||||
Solid,Thermal generation,400.12
|
||||
Solid,Industry,46.477
|
||||
Thermal generation,Electricity grid,525.531
|
||||
Thermal generation,Losses,787.129
|
||||
Thermal generation,District heating,79.329
|
||||
Tidal,Electricity grid,9.452
|
||||
UK land based bioenergy,Bio-conversion,182.01
|
||||
Wave,Electricity grid,19.013
|
||||
Wind,Electricity grid,289.366
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
The idea behind syntax is that a user types `sankey-beta` keyword first, then pastes raw CSV below and get the result.
|
||||
|
||||
It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**:
|
||||
|
||||
- CSV must contain **3 columns only**
|
||||
- It is **allowed** to have **empty lines** without comma separators for visual purposes
|
||||
|
||||
### Basic
|
||||
|
||||
It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly:
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey-beta
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
```
|
||||
|
||||
### Empty Lines
|
||||
|
||||
CSV does not support empty lines without comma delimeters by default. But you can add them if needed:
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
Bio-conversion,Solid,280.322
|
||||
|
||||
Bio-conversion,Gas,81.144
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey-beta
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
Bio-conversion,Solid,280.322
|
||||
|
||||
Bio-conversion,Gas,81.144
|
||||
```
|
||||
|
||||
### Commas
|
||||
|
||||
If you need to have a comma, wrap it in double quotes:
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
```
|
||||
|
||||
### Double Quotes
|
||||
|
||||
If you need to have double quote, put a pair of them inside quoted string:
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey-beta
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize link colors, node alignments and diagram dimensions.
|
||||
|
||||
```html
|
||||
<script>
|
||||
const config = {
|
||||
startOnLoad: true,
|
||||
securityLevel: 'loose',
|
||||
sankey: {
|
||||
width: 800,
|
||||
height: 400,
|
||||
linkColor: 'source',
|
||||
nodeAlignment: 'left',
|
||||
},
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
```
|
||||
|
||||
### Links Coloring
|
||||
|
||||
You can adjust links' color by setting `linkColor` to one of those:
|
||||
|
||||
- `source` - link will be of a source node color
|
||||
- `target` - link will be of a target node color
|
||||
- `gradient` - link color will be smoothly transient between source and target node colors
|
||||
- hex code of color, like `#a1a1a1`
|
||||
|
||||
### Node Alignment
|
||||
|
||||
Graph layout can be changed by setting `nodeAlignment` to:
|
||||
|
||||
- `justify`
|
||||
- `center`
|
||||
- `left`
|
||||
- `right`
|
@ -94,6 +94,43 @@ sequenceDiagram
|
||||
J->>A: Great!
|
||||
```
|
||||
|
||||
### Actor Creation and Destruction (v10.3.0+)
|
||||
|
||||
It is possible to create and destroy actors by messages. To do so, add a create or destroy directive before the message.
|
||||
|
||||
create participant B
|
||||
A --> B: Hello
|
||||
|
||||
Create directives support actor/participant distinction and aliases. The sender or the recipient of a message can be destroyed but only the recipient can be created.
|
||||
|
||||
```mermaid-example
|
||||
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
|
||||
```
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
14
package.json
14
package.json
@ -4,7 +4,7 @@
|
||||
"version": "10.2.4",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.6.5",
|
||||
"packageManager": "pnpm@8.6.10",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@ -78,12 +78,12 @@
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-v8": "^0.32.2",
|
||||
"@vitest/spy": "^0.32.2",
|
||||
"@vitest/ui": "^0.32.2",
|
||||
"@vitest/coverage-v8": "^0.33.0",
|
||||
"@vitest/spy": "^0.33.0",
|
||||
"@vitest/ui": "^0.33.0",
|
||||
"ajv": "^8.12.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"coveralls": "^3.1.1",
|
||||
"cypress": "^12.10.0",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"esbuild": "^0.18.0",
|
||||
@ -119,10 +119,10 @@
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-istanbul": "^4.1.0",
|
||||
"vitest": "^0.32.2"
|
||||
"vitest": "^0.33.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.1"
|
||||
"node": "18.17.0"
|
||||
},
|
||||
"nyc": {
|
||||
"report-dir": "coverage/cypress"
|
||||
|
@ -22,7 +22,6 @@ export const setInfo = (inf) => {
|
||||
info = inf;
|
||||
};
|
||||
|
||||
/** @returns Returns the info flag */
|
||||
export const getInfo = () => {
|
||||
return info;
|
||||
};
|
||||
|
@ -8,7 +8,6 @@ import { log, getConfig, setupGraphViewbox } from './mermaidUtils.js';
|
||||
* @param {any} text
|
||||
* @param {any} id
|
||||
* @param {any} version
|
||||
* @param diagObj
|
||||
*/
|
||||
export const draw = (text, id, version) => {
|
||||
try {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const warning = (s: string) => {
|
||||
// Todo remove debug code
|
||||
// eslint-disable-next-line no-console
|
||||
@ -28,7 +29,6 @@ export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||
export let getConfig: () => object;
|
||||
export let sanitizeText: (str: string) => string;
|
||||
export let commonDb: () => object;
|
||||
// eslint-disable @typescript-eslint/no-explicit-any
|
||||
export let setupGraphViewbox: (
|
||||
graph: any,
|
||||
svgElem: any,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mermaid-js/mermaid-zenuml",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"description": "MermaidJS plugin for ZenUML integration",
|
||||
"module": "dist/mermaid-zenuml.core.mjs",
|
||||
"types": "dist/detector.d.ts",
|
||||
@ -33,7 +33,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zenuml/core": "^3.0.0"
|
||||
"@zenuml/core": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mermaid": "workspace:^"
|
||||
|
@ -4,4 +4,5 @@ export default {
|
||||
'src/docs/**': ['pnpm --filter mermaid run docs:build --git'],
|
||||
'src/docs.mts': ['pnpm --filter mermaid run docs:build --git'],
|
||||
'src/(defaultConfig|config|mermaidAPI).ts': ['pnpm --filter mermaid run docs:build --git'],
|
||||
'src/schemas/config.schema.yaml': ['pnpm --filter mermaid run types:build-config --git'],
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.2.4",
|
||||
"version": "10.3.0",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@ -25,13 +25,18 @@
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --verify",
|
||||
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"ts-node-esm scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"ts-node-esm scripts/docs.cli.mts --watch --vitepress\"",
|
||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||
"docs:release-version": "ts-node-esm scripts/update-release-version.mts",
|
||||
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify",
|
||||
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
|
||||
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
|
||||
"release": "pnpm build",
|
||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
|
||||
},
|
||||
@ -53,13 +58,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
"d3": "^7.4.0",
|
||||
"d3-sankey": "^0.12.3",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "3.0.3",
|
||||
"dompurify": "3.0.5",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
@ -71,8 +79,10 @@
|
||||
"web-worker": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/jsonschema2md": "^7.1.4",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
@ -83,9 +93,9 @@
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"ajv": "^8.11.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"coveralls": "^3.1.1",
|
||||
"cpy-cli": "^4.2.0",
|
||||
"cspell": "^6.31.1",
|
||||
"csstree-validator": "^3.0.0",
|
||||
@ -93,6 +103,7 @@
|
||||
"jison": "^0.4.18",
|
||||
"js-base64": "^3.7.5",
|
||||
"jsdom": "^22.0.0",
|
||||
"json-schema-to-typescript": "^11.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.8.8",
|
||||
@ -105,6 +116,7 @@
|
||||
"typedoc-plugin-markdown": "^3.15.2",
|
||||
"typescript": "^5.0.4",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
"unist-util-visit": "^4.1.2",
|
||||
"vitepress": "^1.0.0-alpha.72",
|
||||
"vitepress-plugin-search": "^1.0.4-alpha.20"
|
||||
},
|
||||
|
252
packages/mermaid/scripts/create-types-from-json-schema.mts
Normal file
252
packages/mermaid/scripts/create-types-from-json-schema.mts
Normal file
@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Script to load Mermaid Config JSON Schema from YAML and to:
|
||||
*
|
||||
* - Validate JSON Schema
|
||||
*
|
||||
* Then to generate:
|
||||
*
|
||||
* - config.types.ts TypeScript file
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import assert from 'node:assert';
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||
import { compile, type JSONSchema } from 'json-schema-to-typescript';
|
||||
|
||||
import _Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||
|
||||
// Workaround for wrong AJV types, see
|
||||
// https://github.com/ajv-validator/ajv/issues/2132#issuecomment-1290409907
|
||||
const Ajv2019 = _Ajv2019 as unknown as typeof _Ajv2019.default;
|
||||
|
||||
// !!! -- The config.type.js file is created by this script -- !!!
|
||||
import type { MermaidConfig } from '../src/config.type.js';
|
||||
|
||||
// options for running the main command
|
||||
const verifyOnly = process.argv.includes('--verify');
|
||||
/** If `true`, automatically `git add` any changes (i.e. during `pnpm run pre-commit`)*/
|
||||
const git = process.argv.includes('--git');
|
||||
|
||||
/**
|
||||
* All of the keys in the mermaid config that have a mermaid diagram config.
|
||||
*/
|
||||
const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
||||
'flowchart',
|
||||
'sequence',
|
||||
'gantt',
|
||||
'journey',
|
||||
'class',
|
||||
'state',
|
||||
'er',
|
||||
'pie',
|
||||
'quadrantChart',
|
||||
'requirement',
|
||||
'mindmap',
|
||||
'timeline',
|
||||
'gitGraph',
|
||||
'c4',
|
||||
'sankey',
|
||||
];
|
||||
|
||||
/**
|
||||
* Loads the MermaidConfig JSON schema YAML file.
|
||||
*
|
||||
* @returns The loaded JSON Schema, use {@link validateSchema} to confirm it is a valid JSON Schema.
|
||||
*/
|
||||
async function loadJsonSchemaFromYaml() {
|
||||
const configSchemaFile = join('src', 'schemas', 'config.schema.yaml');
|
||||
const contentsYaml = await readFile(configSchemaFile, { encoding: 'utf8' });
|
||||
const jsonSchema = load(contentsYaml, {
|
||||
filename: configSchemaFile,
|
||||
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||
schema: JSON_SCHEMA,
|
||||
});
|
||||
return jsonSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the given value is a valid JSON Schema object.
|
||||
*
|
||||
* @param jsonSchema - The value to validate as JSON Schema 2019-09
|
||||
* @throws {Error} if the given object is invalid.
|
||||
*/
|
||||
function validateSchema(jsonSchema: unknown): asserts jsonSchema is JSONSchemaType<MermaidConfig> {
|
||||
if (typeof jsonSchema !== 'object') {
|
||||
throw new Error(`jsonSchema param is not an object: actual type is ${typeof jsonSchema}`);
|
||||
}
|
||||
if (jsonSchema === null) {
|
||||
throw new Error('jsonSchema param must not be null');
|
||||
}
|
||||
const ajv = new Ajv2019({
|
||||
allErrors: true,
|
||||
allowUnionTypes: true,
|
||||
strict: true,
|
||||
});
|
||||
|
||||
ajv.addKeyword({
|
||||
keyword: 'meta:enum', // used by jsonschema2md (in docs.mts script)
|
||||
errors: false,
|
||||
});
|
||||
ajv.addKeyword({
|
||||
keyword: 'tsType', // used by json-schema-to-typescript
|
||||
errors: false,
|
||||
});
|
||||
|
||||
ajv.compile(jsonSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a typescript definition from a JSON Schema using json-schema-to-typescript.
|
||||
*
|
||||
* @param mermaidConfigSchema - The input JSON Schema.
|
||||
*/
|
||||
async function generateTypescript(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
|
||||
/**
|
||||
* Replace all usages of `allOf` with `extends`.
|
||||
*
|
||||
* `extends` is only valid JSON Schema in very old versions of JSON Schema.
|
||||
* However, json-schema-to-typescript creates much nicer types when using
|
||||
* `extends`, so we should use them instead when possible.
|
||||
*
|
||||
* @param schema - The input schema.
|
||||
* @returns The schema with `allOf` replaced with `extends`.
|
||||
*/
|
||||
function replaceAllOfWithExtends(schema: JSONSchemaType<Record<string, any>>) {
|
||||
if (schema['allOf']) {
|
||||
const { allOf, ...schemaWithoutAllOf } = schema;
|
||||
return {
|
||||
...schemaWithoutAllOf,
|
||||
extends: allOf,
|
||||
};
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* For backwards compatibility with older Mermaid Typescript defs,
|
||||
* we need to make all value optional instead of required.
|
||||
*
|
||||
* This is because the `MermaidConfig` type is used as an input, and everything is optional,
|
||||
* since all the required values have default values.s
|
||||
*
|
||||
* In the future, we should make make the input to Mermaid `Partial<MermaidConfig>`.
|
||||
*
|
||||
* @todo TODO: Remove this function when Mermaid releases a new breaking change.
|
||||
* @param schema - The input schema.
|
||||
* @returns The schema with all required values removed.
|
||||
*/
|
||||
function removeRequired(schema: JSONSchemaType<Record<string, any>>) {
|
||||
return { ...schema, required: [] };
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a temporary hack to control the order the types are generated in.
|
||||
*
|
||||
* By default, json-schema-to-typescript outputs the $defs in the order they
|
||||
* are used, then any unused schemas at the end.
|
||||
*
|
||||
* **The only purpose of this function is to make the `git diff` simpler**
|
||||
* **We should remove this later to simplify the code**
|
||||
*
|
||||
* @todo TODO: Remove this function in a future PR.
|
||||
* @param schema - The input schema.
|
||||
* @returns The schema with all `$ref`s removed.
|
||||
*/
|
||||
function unrefSubschemas(schema: JSONSchemaType<Record<string, any>>) {
|
||||
return {
|
||||
...schema,
|
||||
properties: Object.fromEntries(
|
||||
Object.entries(schema.properties).map(([key, propertySchema]) => {
|
||||
if (MERMAID_CONFIG_DIAGRAM_KEYS.includes(key)) {
|
||||
const { $ref, ...propertySchemaWithoutRef } = propertySchema as JSONSchemaType<unknown>;
|
||||
if ($ref === undefined) {
|
||||
throw Error(
|
||||
`subSchema ${key} is in MERMAID_CONFIG_DIAGRAM_KEYS but does not have a $ref field`
|
||||
);
|
||||
}
|
||||
const [
|
||||
_root, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
_defs, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
defName,
|
||||
] = $ref.split('/');
|
||||
return [
|
||||
key,
|
||||
{
|
||||
...propertySchemaWithoutRef,
|
||||
tsType: defName,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [key, propertySchema];
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
assert.ok(mermaidConfigSchema.$defs);
|
||||
const modifiedSchema = {
|
||||
...unrefSubschemas(removeRequired(mermaidConfigSchema)),
|
||||
|
||||
$defs: Object.fromEntries(
|
||||
Object.entries(mermaidConfigSchema.$defs).map(([key, subSchema]) => {
|
||||
return [key, removeRequired(replaceAllOfWithExtends(subSchema))];
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
const typescriptFile = await compile(
|
||||
modifiedSchema as JSONSchema, // json-schema-to-typescript only allows JSON Schema 4 as input type
|
||||
'MermaidConfig',
|
||||
{
|
||||
additionalProperties: false, // in JSON Schema 2019-09, these are called `unevaluatedProperties`
|
||||
unreachableDefinitions: true, // definition for FontConfig is unreachable
|
||||
}
|
||||
);
|
||||
|
||||
// TODO, should we somehow use the functions from `docs.mts` instead?
|
||||
if (verifyOnly) {
|
||||
const originalFile = await readFile('./src/config.type.ts', { encoding: 'utf-8' });
|
||||
if (typescriptFile !== originalFile) {
|
||||
console.error('❌ Error: ./src/config.type.ts will be changed.');
|
||||
console.error("Please run 'pnpm run --filter mermaid types:build-config' to update this");
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log('✅ ./src/config.type.ts will be unchanged');
|
||||
}
|
||||
} else {
|
||||
console.log('Writing typescript file to ./src/config.type.ts');
|
||||
await writeFile('./src/config.type.ts', typescriptFile, { encoding: 'utf8' });
|
||||
if (git) {
|
||||
console.log('📧 Git: Adding ./src/config.type.ts changed');
|
||||
await promisify(execFile)('git', ['add', './src/config.type.ts']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Main function */
|
||||
async function main() {
|
||||
if (verifyOnly) {
|
||||
console.log(
|
||||
'Verifying that ./src/config.type.ts is in sync with src/schemas/config.schema.yaml'
|
||||
);
|
||||
}
|
||||
|
||||
const configJsonSchema = await loadJsonSchemaFromYaml();
|
||||
|
||||
validateSchema(configJsonSchema);
|
||||
|
||||
// Generate types from JSON Schema
|
||||
await generateTypescript(configJsonSchema);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
3
packages/mermaid/scripts/docs.cli.mts
Normal file
3
packages/mermaid/scripts/docs.cli.mts
Normal file
@ -0,0 +1,3 @@
|
||||
import { processDocs } from './docs.mjs';
|
||||
|
||||
void processDocs();
|
@ -27,14 +27,21 @@
|
||||
* get their absolute paths. Ensures that the location of those 2 directories is not dependent on
|
||||
* where this file resides.
|
||||
*
|
||||
* @todo Write a test file for this. (Will need to be able to deal .mts file. Jest has trouble with
|
||||
* it.)
|
||||
*/
|
||||
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||
import { default as schemaLoader } from '@adobe/jsonschema2md/lib/schemaProxy.js';
|
||||
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||
import { default as traverseSchemas } from '@adobe/jsonschema2md/lib/traverseSchema.js';
|
||||
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||
import { default as buildMarkdownFromSchema } from '@adobe/jsonschema2md/lib/markdownBuilder.js';
|
||||
// @ts-ignore: we're importing internal jsonschema2md functions
|
||||
import { default as jsonSchemaReadmeBuilder } from '@adobe/jsonschema2md/lib/readmeBuilder.js';
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, rmdirSync } from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
import { globby } from 'globby';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import type { Code, Root } from 'mdast';
|
||||
import { dump, load, JSON_SCHEMA } from 'js-yaml';
|
||||
import type { Code, ListItem, Root, Text, YAML } from 'mdast';
|
||||
import { posix, dirname, relative, join } from 'path';
|
||||
import prettier from 'prettier';
|
||||
import { remark } from 'remark';
|
||||
@ -44,10 +51,11 @@ import chokidar from 'chokidar';
|
||||
import mm from 'micromatch';
|
||||
// @ts-ignore No typescript declaration file
|
||||
import flatmap from 'unist-util-flatmap';
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
const MERMAID_MAJOR_VERSION = (
|
||||
JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string
|
||||
).split('.')[0];
|
||||
export const MERMAID_RELEASE_VERSION = JSON.parse(readFileSync('../mermaid/package.json', 'utf8'))
|
||||
.version as string;
|
||||
const MERMAID_MAJOR_VERSION = MERMAID_RELEASE_VERSION.split('.')[0];
|
||||
const CDN_URL = 'https://cdn.jsdelivr.net/npm'; // 'https://unpkg.com';
|
||||
|
||||
const MERMAID_KEYWORD = 'mermaid';
|
||||
@ -70,7 +78,7 @@ const vitepress: boolean = process.argv.includes('--vitepress');
|
||||
const noHeader: boolean = process.argv.includes('--noHeader') || vitepress;
|
||||
|
||||
// These paths are from the root of the mono-repo, not from the mermaid subdirectory
|
||||
const SOURCE_DOCS_DIR = 'src/docs';
|
||||
export const SOURCE_DOCS_DIR = 'src/docs';
|
||||
const FINAL_DOCS_DIR = vitepress ? 'src/vitepress' : '../../docs';
|
||||
|
||||
const LOGMSG_TRANSFORMED = 'transformed';
|
||||
@ -122,7 +130,7 @@ const changeToFinalDocDir = (file: string): string => {
|
||||
const logWasOrShouldBeTransformed = (filename: string, wasCopied: boolean) => {
|
||||
const changeMsg = wasCopied ? LOGMSG_TRANSFORMED : LOGMSG_TO_BE_TRANSFORMED;
|
||||
let logMsg: string;
|
||||
logMsg = ` File ${changeMsg}: ${filename}`;
|
||||
logMsg = ` File ${changeMsg}: ${filename.replace(FINAL_DOCS_DIR, SOURCE_DOCS_DIR)}`;
|
||||
if (wasCopied) {
|
||||
logMsg += LOGMSG_COPIED;
|
||||
}
|
||||
@ -150,13 +158,14 @@ const copyTransformedContents = (filename: string, doCopy = false, transformedCo
|
||||
}
|
||||
|
||||
filesTransformed.add(fileInFinalDocDir);
|
||||
|
||||
if (doCopy) {
|
||||
writeFileSync(fileInFinalDocDir, newBuffer);
|
||||
}
|
||||
logWasOrShouldBeTransformed(fileInFinalDocDir, doCopy);
|
||||
};
|
||||
|
||||
const readSyncedUTF8file = (filename: string): string => {
|
||||
export const readSyncedUTF8file = (filename: string): string => {
|
||||
return readFileSync(filename, 'utf8');
|
||||
};
|
||||
|
||||
@ -207,6 +216,8 @@ interface TransformMarkdownAstOptions {
|
||||
originalFilename: string;
|
||||
/** If `true`, add a warning that the file is autogenerated */
|
||||
addAutogeneratedWarning?: boolean;
|
||||
/** If `true`, adds an `editLink: "https://..."` YAML frontmatter field */
|
||||
addEditLink?: boolean;
|
||||
/**
|
||||
* If `true`, remove the YAML metadata from the Markdown input.
|
||||
* Generally, YAML metadata is only used for Vitepress.
|
||||
@ -229,6 +240,7 @@ interface TransformMarkdownAstOptions {
|
||||
export function transformMarkdownAst({
|
||||
originalFilename,
|
||||
addAutogeneratedWarning,
|
||||
addEditLink,
|
||||
removeYAML,
|
||||
}: TransformMarkdownAstOptions) {
|
||||
return (tree: Root, _file?: any): Root => {
|
||||
@ -268,6 +280,27 @@ export function transformMarkdownAst({
|
||||
}
|
||||
}
|
||||
|
||||
if (addEditLink) {
|
||||
// add originalFilename as custom editLink in YAML frontmatter
|
||||
let yamlFrontMatter: YAML;
|
||||
if (astWithTransformedBlocks.children[0].type === 'yaml') {
|
||||
yamlFrontMatter = astWithTransformedBlocks.children[0];
|
||||
} else {
|
||||
yamlFrontMatter = {
|
||||
type: 'yaml',
|
||||
value: '',
|
||||
};
|
||||
astWithTransformedBlocks.children.unshift(yamlFrontMatter);
|
||||
}
|
||||
const filePathFromRoot = posix.join('packages/mermaid', originalFilename);
|
||||
yamlFrontMatter.value = dump({
|
||||
...(load(yamlFrontMatter.value, { schema: JSON_SCHEMA }) as
|
||||
| Record<string, unknown>
|
||||
| undefined),
|
||||
editLink: `https://github.com/mermaid-js/mermaid/edit/develop/${filePathFromRoot}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (removeYAML) {
|
||||
const firstNode = astWithTransformedBlocks.children[0];
|
||||
if (firstNode.type == 'yaml') {
|
||||
@ -304,6 +337,7 @@ const transformMarkdown = (file: string) => {
|
||||
// mermaid project specific plugin
|
||||
originalFilename: file,
|
||||
addAutogeneratedWarning: !noHeader,
|
||||
addEditLink: noHeader,
|
||||
removeYAML: !noHeader,
|
||||
})
|
||||
.processSync(doc)
|
||||
@ -321,6 +355,115 @@ const transformMarkdown = (file: string) => {
|
||||
copyTransformedContents(file, !verifyOnly, formatted);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms the given JSON Schema into Markdown documentation
|
||||
*/
|
||||
async function transformJsonSchema(file: string) {
|
||||
const yamlContents = readSyncedUTF8file(file);
|
||||
const jsonSchema = load(yamlContents, {
|
||||
filename: file,
|
||||
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
||||
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
||||
schema: JSON_SCHEMA,
|
||||
});
|
||||
|
||||
/** Location of the `schema.yaml` files */
|
||||
const SCHEMA_INPUT_DIR = 'src/schemas/';
|
||||
/**
|
||||
* Location to store the generated `schema.json` file for the website
|
||||
*
|
||||
* Because Vitepress doesn't handle bundling `.json` files properly, we need
|
||||
* to instead place it into a `public/` subdirectory.
|
||||
*/
|
||||
const SCHEMA_OUTPUT_DIR = 'src/docs/public/schemas/';
|
||||
const VITEPRESS_PUBLIC_DIR = 'src/docs/public';
|
||||
/**
|
||||
* Location to store the generated Schema Markdown docs.
|
||||
* Links to JSON Schemas should automatically be rewritten to point to
|
||||
* `SCHEMA_OUTPUT_DIR`.
|
||||
*/
|
||||
const SCHEMA_MARKDOWN_OUTPUT_DIR = join('src', 'docs', 'config', 'schema-docs');
|
||||
|
||||
// write .schema.json files
|
||||
const jsonFileName = file
|
||||
.replace('.schema.yaml', '.schema.json')
|
||||
.replace(SCHEMA_INPUT_DIR, SCHEMA_OUTPUT_DIR);
|
||||
copyTransformedContents(jsonFileName, !verifyOnly, JSON.stringify(jsonSchema, undefined, 2));
|
||||
|
||||
const schemas = traverseSchemas([schemaLoader()(jsonFileName, jsonSchema)]);
|
||||
|
||||
// ignore output of this function
|
||||
// for some reason, without calling this function, we get some broken links
|
||||
// this is probably a bug in @adobe/jsonschema2md
|
||||
jsonSchemaReadmeBuilder({ readme: true })(schemas);
|
||||
|
||||
// write Markdown files
|
||||
const markdownFiles = buildMarkdownFromSchema({
|
||||
header: true,
|
||||
// links,
|
||||
includeProperties: ['tsType'], // Custom TypeScript type
|
||||
exampleFormat: 'json',
|
||||
// skipProperties,
|
||||
/**
|
||||
* Automatically rewrite schema paths passed to `schemaLoader`
|
||||
* (e.g. src/docs/schemas/config.schema.json)
|
||||
* to relative links (e.g. /schemas/config.schema.json)
|
||||
*
|
||||
* See https://vitepress.vuejs.org/guide/asset-handling
|
||||
*
|
||||
* @param origin - Original schema path (relative to this script).
|
||||
* @returns New absolute Vitepress path to schema
|
||||
*/
|
||||
rewritelinks: (origin: string) => {
|
||||
return `/${relative(VITEPRESS_PUBLIC_DIR, origin)}`;
|
||||
},
|
||||
})(schemas);
|
||||
|
||||
for (const [name, markdownAst] of Object.entries(markdownFiles)) {
|
||||
/*
|
||||
* Converts list entries of shape '- tsType: () => Partial<FontConfig>'
|
||||
* into '- tsType: `() => Partial<FontConfig>`' (e.g. escaping with back-ticks),
|
||||
* as otherwise VitePress doesn't like the <FontConfig> bit.
|
||||
*/
|
||||
visit(markdownAst as Root, 'listItem', (listEntry: ListItem) => {
|
||||
let listText: Text;
|
||||
const blockItem = listEntry.children[0];
|
||||
if (blockItem.type === 'paragraph' && blockItem.children[0].type === 'text') {
|
||||
listText = blockItem.children[0];
|
||||
} // @ts-expect-error: MD AST output from @adobe/jsonschema2md is technically wrong
|
||||
else if (blockItem.type === 'text') {
|
||||
listText = blockItem;
|
||||
} else {
|
||||
return; // skip
|
||||
}
|
||||
|
||||
if (listText.value.startsWith('tsType: ')) {
|
||||
listText.value = listText.value.replace(/tsType: (.*)/g, 'tsType: `$1`');
|
||||
}
|
||||
});
|
||||
|
||||
const transformer = remark()
|
||||
.use(remarkGfm)
|
||||
.use(remarkFrontmatter, ['yaml']) // support YAML front-matter in Markdown
|
||||
.use(transformMarkdownAst, {
|
||||
// mermaid project specific plugin
|
||||
originalFilename: file,
|
||||
addAutogeneratedWarning: !noHeader,
|
||||
addEditLink: noHeader,
|
||||
removeYAML: !noHeader,
|
||||
});
|
||||
|
||||
const transformed = transformer.stringify(await transformer.run(markdownAst as Root));
|
||||
|
||||
const formatted = prettier.format(transformed, {
|
||||
parser: 'markdown',
|
||||
...prettierConfig,
|
||||
});
|
||||
const newFileName = join(SCHEMA_MARKDOWN_OUTPUT_DIR, `${name}.md`);
|
||||
copyTransformedContents(newFileName, !verifyOnly, formatted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an HTML file and write the transformed file to the directory for published
|
||||
* documentation
|
||||
@ -361,26 +504,26 @@ const transformHtml = (filename: string) => {
|
||||
copyTransformedContents(filename, !verifyOnly, formattedHTML);
|
||||
};
|
||||
|
||||
const getGlobs = (globs: string[]): string[] => {
|
||||
globs.push(
|
||||
'!**/dist',
|
||||
'!**/redirect.spec.ts',
|
||||
'!**/landing',
|
||||
'!**/node_modules',
|
||||
'!**/user-avatars'
|
||||
);
|
||||
export const getGlobs = (globs: string[]): string[] => {
|
||||
globs.push('!**/dist/**', '!**/redirect.spec.ts', '!**/landing/**', '!**/node_modules/**');
|
||||
if (!vitepress) {
|
||||
globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md', '!**/package.json');
|
||||
globs.push(
|
||||
'!**/.vitepress/**',
|
||||
'!**/vite.config.ts',
|
||||
'!src/docs/index.md',
|
||||
'!**/package.json',
|
||||
'!**/user-avatars/**'
|
||||
);
|
||||
}
|
||||
return globs;
|
||||
};
|
||||
|
||||
const getFilesFromGlobs = async (globs: string[]): Promise<string[]> => {
|
||||
export const getFilesFromGlobs = async (globs: string[]): Promise<string[]> => {
|
||||
return await globby(globs, { dot: true });
|
||||
};
|
||||
|
||||
/** Main method (entry point) */
|
||||
const main = async () => {
|
||||
export const processDocs = async () => {
|
||||
if (verifyOnly) {
|
||||
console.log('Verifying that all files are in sync with the source files');
|
||||
}
|
||||
@ -388,6 +531,14 @@ const main = async () => {
|
||||
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
|
||||
const action = verifyOnly ? 'Verifying' : 'Transforming';
|
||||
|
||||
if (vitepress) {
|
||||
console.log(`${action} 1 .schema.yaml file`);
|
||||
await transformJsonSchema('src/schemas/config.schema.yaml');
|
||||
} else {
|
||||
// skip because this creates so many Markdown files that it lags git
|
||||
console.log('Skipping 1 .schema.yaml file');
|
||||
}
|
||||
|
||||
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
|
||||
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
|
||||
console.log(`${action} ${mdFiles.length} markdown files...`);
|
||||
@ -450,5 +601,3 @@ const main = async () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void main();
|
@ -105,6 +105,29 @@ This Markdown should be kept.
|
||||
});
|
||||
});
|
||||
|
||||
it('should add an editLink in the YAML frontmatter if `addEditLink: true`', async () => {
|
||||
const contents = `---
|
||||
title: Flowcharts Syntax
|
||||
---
|
||||
|
||||
This Markdown should be kept.
|
||||
`;
|
||||
const withYaml = (
|
||||
await remarkBuilder()
|
||||
.use(transformMarkdownAst, { originalFilename, addEditLink: true })
|
||||
.process(contents)
|
||||
).toString();
|
||||
expect(withYaml).toEqual(`---
|
||||
title: Flowcharts Syntax
|
||||
editLink: >-
|
||||
https://github.com/mermaid-js/mermaid/edit/develop/packages/mermaid/example-input-filename.md
|
||||
|
||||
---
|
||||
|
||||
This Markdown should be kept.
|
||||
`);
|
||||
});
|
||||
|
||||
describe('transformToBlockQuote', () => {
|
||||
// TODO Is there a way to test this with --vitepress given as a process argument?
|
||||
|
53
packages/mermaid/scripts/update-release-version.mts
Normal file
53
packages/mermaid/scripts/update-release-version.mts
Normal file
@ -0,0 +1,53 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
/**
|
||||
* @file Update the MERMAID_RELEASE_VERSION placeholder in the documentation source files with the current version of mermaid.
|
||||
* So contributors adding new features will only have to add the placeholder and not worry about updating the version number.
|
||||
*
|
||||
*/
|
||||
import { posix } from 'path';
|
||||
import {
|
||||
getGlobs,
|
||||
getFilesFromGlobs,
|
||||
SOURCE_DOCS_DIR,
|
||||
readSyncedUTF8file,
|
||||
MERMAID_RELEASE_VERSION,
|
||||
} from './docs.mjs';
|
||||
import { writeFile } from 'fs/promises';
|
||||
|
||||
const verifyOnly: boolean = process.argv.includes('--verify');
|
||||
const versionPlaceholder = '<MERMAID_RELEASE_VERSION>';
|
||||
|
||||
const main = async () => {
|
||||
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
|
||||
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
|
||||
mdFileGlobs.push('!**/community/development.md');
|
||||
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
|
||||
mdFiles.sort();
|
||||
const mdFilesWithPlaceholder: string[] = [];
|
||||
for (const mdFile of mdFiles) {
|
||||
const content = readSyncedUTF8file(mdFile);
|
||||
if (content.includes(versionPlaceholder)) {
|
||||
mdFilesWithPlaceholder.push(mdFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (mdFilesWithPlaceholder.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (verifyOnly) {
|
||||
console.log(
|
||||
`${mdFilesWithPlaceholder.length} file(s) were found with the placeholder ${versionPlaceholder}. Run \`pnpm --filter mermaid docs:release-version\` to update them.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const mdFile of mdFilesWithPlaceholder) {
|
||||
const content = readSyncedUTF8file(mdFile);
|
||||
const newContent = content.replace(versionPlaceholder, MERMAID_RELEASE_VERSION);
|
||||
await writeFile(mdFile, newContent);
|
||||
}
|
||||
};
|
||||
|
||||
void main();
|
@ -1,27 +1,24 @@
|
||||
import { MockedD3 } from './tests/MockedD3.js';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
|
||||
import { D3Element } from './mermaidAPI.js';
|
||||
import type { D3Element } from './mermaidAPI.js';
|
||||
|
||||
describe('accessibility', () => {
|
||||
const fauxSvgNode = new MockedD3();
|
||||
const fauxSvgNode: MockedD3 = new MockedD3();
|
||||
|
||||
describe('setA11yDiagramInfo', () => {
|
||||
it('sets the svg element role to "graphics-document document"', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should set svg element role to "graphics-document document"', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document');
|
||||
});
|
||||
|
||||
it('sets the aria-roledescription to the diagram type', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should set aria-roledescription to the diagram type', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
setA11yDiagramInfo(fauxSvgNode, 'flowchart');
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart');
|
||||
});
|
||||
|
||||
it('does not set the aria-roledescription if the diagram type is empty', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should not set aria-roledescription if the diagram type is empty', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
setA11yDiagramInfo(fauxSvgNode, '');
|
||||
expect(svgAttrSpy).toHaveBeenCalledTimes(1);
|
||||
@ -32,8 +29,8 @@ describe('accessibility', () => {
|
||||
describe('addSVGa11yTitleDescription', () => {
|
||||
const givenId = 'theBaseId';
|
||||
|
||||
describe('with the given svg d3 object:', () => {
|
||||
it('does nothing if there is no insert defined', () => {
|
||||
describe('with svg d3 object', () => {
|
||||
it('should do nothing if there is no insert defined', () => {
|
||||
const noInsertSvg = {
|
||||
attr: vi.fn(),
|
||||
};
|
||||
@ -42,26 +39,25 @@ describe('accessibility', () => {
|
||||
expect(noInsertAttrSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ----------------
|
||||
// Convenience functions to DRY up the spec
|
||||
// convenience functions to DRY up the spec
|
||||
|
||||
function expectAriaLabelledByIsTitleId(
|
||||
function expectAriaLabelledByItTitleId(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
title: string | undefined,
|
||||
desc: string | undefined,
|
||||
givenId: string
|
||||
) {
|
||||
): void {
|
||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
|
||||
}
|
||||
|
||||
function expectAriaDescribedByIsDescId(
|
||||
function expectAriaDescribedByItDescId(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
title: string | undefined,
|
||||
desc: string | undefined,
|
||||
givenId: string
|
||||
) {
|
||||
): void {
|
||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
|
||||
@ -69,154 +65,148 @@ describe('accessibility', () => {
|
||||
|
||||
function a11yTitleTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
title: string | undefined,
|
||||
desc: string | undefined,
|
||||
givenId: string,
|
||||
callNumber: number
|
||||
) {
|
||||
): void {
|
||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
|
||||
}
|
||||
|
||||
function a11yDescTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
title: string | undefined,
|
||||
desc: string | undefined,
|
||||
givenId: string,
|
||||
callNumber: number
|
||||
) {
|
||||
): void {
|
||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
|
||||
}
|
||||
|
||||
function a11yTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
_svgD3Node: D3Element,
|
||||
title: string | undefined,
|
||||
desc: string | undefined,
|
||||
givenId: string,
|
||||
callNumber: number,
|
||||
expectedPrefix: string,
|
||||
expectedText: string | null | undefined
|
||||
) {
|
||||
const fauxInsertedD3 = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
expectedText: string | undefined
|
||||
): void {
|
||||
const fauxInsertedD3: MockedD3 = new MockedD3();
|
||||
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
|
||||
const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
|
||||
const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');
|
||||
|
||||
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
|
||||
expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
|
||||
expect(svginsertpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
|
||||
expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
|
||||
expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText);
|
||||
}
|
||||
// ----------------
|
||||
|
||||
describe('given an a11y title', () => {
|
||||
describe('with a11y title', () => {
|
||||
const a11yTitle = 'a11y title';
|
||||
|
||||
describe('given an a11y description', () => {
|
||||
describe('with a11y description', () => {
|
||||
const a11yDesc = 'a11y description';
|
||||
|
||||
it('sets aria-labelledby to the title id inserted as a child', () => {
|
||||
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
it('shold set aria-labelledby to the title id inserted as a child', () => {
|
||||
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
});
|
||||
|
||||
it('sets aria-describedby to the description id inserted as a child', () => {
|
||||
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
it('should set aria-describedby to the description id inserted as a child', () => {
|
||||
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
});
|
||||
|
||||
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
|
||||
it('should insert title tag as the first child with the text set to the accTitle given', () => {
|
||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
|
||||
});
|
||||
|
||||
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`no a11y description`, () => {
|
||||
describe(`without a11y description`, () => {
|
||||
const a11yDesc = undefined;
|
||||
|
||||
it('sets aria-labelledby to the title id inserted as a child', () => {
|
||||
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
it('should set aria-labelledby to the title id inserted as a child', () => {
|
||||
expectAriaLabelledByItTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
});
|
||||
|
||||
it('no aria-describedby is set', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should not set aria-describedby', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
||||
});
|
||||
|
||||
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
|
||||
it('should insert title tag as the first child with the text set to the accTitle given', () => {
|
||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||
});
|
||||
|
||||
it('no description tag is inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
it('should not insert description tag', () => {
|
||||
const fauxTitle: MockedD3 = new MockedD3();
|
||||
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no a11y title', () => {
|
||||
describe('without a11y title', () => {
|
||||
const a11yTitle = undefined;
|
||||
|
||||
describe('given an a11y description', () => {
|
||||
describe('with a11y description', () => {
|
||||
const a11yDesc = 'a11y description';
|
||||
|
||||
it('no aria-labelledby is set', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should not set aria-labelledby', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
||||
});
|
||||
|
||||
it('no title tag inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
it('should not insert title tag', () => {
|
||||
const fauxTitle: MockedD3 = new MockedD3();
|
||||
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
});
|
||||
|
||||
it('sets aria-describedby to the description id inserted as a child', () => {
|
||||
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
it('should set aria-describedby to the description id inserted as a child', () => {
|
||||
expectAriaDescribedByItDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
});
|
||||
|
||||
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||
it('should insert desc tag as the 2nd child with the text set to accDescription given', () => {
|
||||
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no a11y description', () => {
|
||||
describe('without a11y description', () => {
|
||||
const a11yDesc = undefined;
|
||||
|
||||
it('no aria-labelledby is set', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should not set aria-labelledby', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
||||
});
|
||||
|
||||
it('no aria-describedby is set', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
it('should not set aria-describedby', () => {
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
||||
});
|
||||
|
||||
it('no title tag inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
it('should not insert title tag', () => {
|
||||
const fauxTitle: MockedD3 = new MockedD3();
|
||||
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
expect(svginsertpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
});
|
||||
|
||||
it('no description tag inserted', () => {
|
||||
const fauxDesc = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
|
||||
it('should not insert description tag', () => {
|
||||
const fauxDesc: MockedD3 = new MockedD3();
|
||||
const svginsertpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
expect(svginsertpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,11 @@
|
||||
/**
|
||||
* Accessibility (a11y) functions, types, helpers
|
||||
* Accessibility (a11y) functions, types, helpers.
|
||||
*
|
||||
* @see https://www.w3.org/WAI/
|
||||
* @see https://www.w3.org/TR/wai-aria-1.1/
|
||||
* @see https://www.w3.org/TR/svg-aam-1.0/
|
||||
*
|
||||
*/
|
||||
import { D3Element } from './mermaidAPI.js';
|
||||
|
||||
import isEmpty from 'lodash-es/isEmpty.js';
|
||||
import type { D3Element } from './mermaidAPI.js';
|
||||
|
||||
/**
|
||||
* SVG element role:
|
||||
@ -21,50 +19,47 @@ import isEmpty from 'lodash-es/isEmpty.js';
|
||||
const SVG_ROLE = 'graphics-document document';
|
||||
|
||||
/**
|
||||
* Add role and aria-roledescription to the svg element
|
||||
* Add role and aria-roledescription to the svg element.
|
||||
*
|
||||
* @param svg - d3 object that contains the SVG HTML element
|
||||
* @param diagramType - diagram name for to the aria-roledescription
|
||||
*/
|
||||
export function setA11yDiagramInfo(svg: D3Element, diagramType: string | null | undefined) {
|
||||
export function setA11yDiagramInfo(svg: D3Element, diagramType: string) {
|
||||
svg.attr('role', SVG_ROLE);
|
||||
if (!isEmpty(diagramType)) {
|
||||
if (diagramType !== '') {
|
||||
svg.attr('aria-roledescription', diagramType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an accessible title and/or description element to a chart.
|
||||
* The title is usually not displayed and the description is never displayed.
|
||||
*
|
||||
* The following charts display their title as a visual and accessibility element: gantt
|
||||
* The following charts display their title as a visual and accessibility element: gantt.
|
||||
*
|
||||
* @param svg - d3 node to insert the a11y title and desc info
|
||||
* @param a11yTitle - a11y title. null and undefined are meaningful: means to skip it
|
||||
* @param a11yDesc - a11y description. null and undefined are meaningful: means to skip it
|
||||
* @param a11yTitle - a11y title. undefined or empty strings mean to skip them
|
||||
* @param a11yDesc - a11y description. undefined or empty strings mean to skip them
|
||||
* @param baseId - id used to construct the a11y title and description id
|
||||
*/
|
||||
export function addSVGa11yTitleDescription(
|
||||
svg: D3Element,
|
||||
a11yTitle: string | null | undefined,
|
||||
a11yDesc: string | null | undefined,
|
||||
a11yTitle: string | undefined,
|
||||
a11yDesc: string | undefined,
|
||||
baseId: string
|
||||
) {
|
||||
): void {
|
||||
if (svg.insert === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (a11yTitle || a11yDesc) {
|
||||
if (a11yDesc) {
|
||||
const descId = 'chart-desc-' + baseId;
|
||||
svg.attr('aria-describedby', descId);
|
||||
svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc);
|
||||
}
|
||||
if (a11yTitle) {
|
||||
const titleId = 'chart-title-' + baseId;
|
||||
svg.attr('aria-labelledby', titleId);
|
||||
svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
if (a11yDesc) {
|
||||
const descId = `chart-desc-${baseId}`;
|
||||
svg.attr('aria-describedby', descId);
|
||||
svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc);
|
||||
}
|
||||
if (a11yTitle) {
|
||||
const titleId = `chart-title-${baseId}`;
|
||||
svg.attr('aria-labelledby', titleId);
|
||||
svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle);
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
import * as configApi from './config.js';
|
||||
|
||||
describe('when working with site config', function () {
|
||||
beforeEach(() => {
|
||||
// Resets the site config to default config
|
||||
configApi.setSiteConfig({});
|
||||
});
|
||||
it('should set site config and config properly', function () {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
let config_1 = configApi.getSiteConfig();
|
||||
let config_2 = configApi.getConfig();
|
||||
expect(config_1.foo).toEqual(config_0.foo);
|
||||
expect(config_1.bar).toEqual(config_0.bar);
|
||||
expect(config_1).toEqual(config_2);
|
||||
});
|
||||
it('should respect secure keys when applying directives', function () {
|
||||
let config_0 = {
|
||||
foo: 'bar',
|
||||
bar: 'cant-be-changed',
|
||||
secure: [...configApi.defaultConfig.secure, 'bar'],
|
||||
};
|
||||
configApi.setSiteConfig(config_0);
|
||||
const directive = { foo: 'baf', bar: 'should-not-be-allowed' };
|
||||
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
expect(cfg.foo).toEqual(directive.foo);
|
||||
expect(cfg.bar).toBe(config_0.bar);
|
||||
});
|
||||
it('should set reset config properly', function () {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
let config_1 = { foo: 'baf' };
|
||||
configApi.setConfig(config_1);
|
||||
let config_2 = configApi.getConfig();
|
||||
expect(config_2.foo).toEqual(config_1.foo);
|
||||
configApi.reset();
|
||||
let config_3 = configApi.getConfig();
|
||||
expect(config_3.foo).toEqual(config_0.foo);
|
||||
let config_4 = configApi.getSiteConfig();
|
||||
expect(config_4.foo).toEqual(config_0.foo);
|
||||
});
|
||||
it('should set global reset config properly', function () {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
let config_1 = configApi.getSiteConfig();
|
||||
expect(config_1.foo).toEqual(config_0.foo);
|
||||
let config_2 = configApi.getConfig();
|
||||
expect(config_2.foo).toEqual(config_0.foo);
|
||||
configApi.setConfig({ foobar: 'bar0' });
|
||||
let config_3 = configApi.getConfig();
|
||||
expect(config_3.foobar).toEqual('bar0');
|
||||
configApi.reset();
|
||||
let config_4 = configApi.getConfig();
|
||||
expect(config_4.foobar).toBeUndefined();
|
||||
});
|
||||
});
|
72
packages/mermaid/src/config.spec.ts
Normal file
72
packages/mermaid/src/config.spec.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import * as configApi from './config.js';
|
||||
|
||||
describe('when working with site config', function () {
|
||||
beforeEach(() => {
|
||||
// Resets the site config to default config
|
||||
configApi.setSiteConfig({});
|
||||
});
|
||||
it('should set site config and config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
const config_2 = configApi.getConfig();
|
||||
expect(config_1.fontFamily).toEqual(config_0.fontFamily);
|
||||
expect(config_1.fontSize).toEqual(config_0.fontSize);
|
||||
expect(config_1).toEqual(config_2);
|
||||
});
|
||||
it('should respect secure keys when applying directives', function () {
|
||||
const config_0 = {
|
||||
fontFamily: 'foo-font',
|
||||
fontSize: 12345, // can't be changed
|
||||
secure: [...configApi.defaultConfig.secure!, 'fontSize'],
|
||||
};
|
||||
configApi.setSiteConfig(config_0);
|
||||
const directive = { fontFamily: 'baf', fontSize: 54321 /* fontSize shouldn't be changed */ };
|
||||
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
expect(cfg.fontFamily).toEqual(directive.fontFamily);
|
||||
expect(cfg.fontSize).toBe(config_0.fontSize);
|
||||
});
|
||||
it('should allow setting partial options', function () {
|
||||
const defaultConfig = configApi.getConfig();
|
||||
|
||||
configApi.setConfig({
|
||||
quadrantChart: {
|
||||
chartHeight: 600,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedConfig = configApi.getConfig();
|
||||
|
||||
// deep options we didn't update should remain the same
|
||||
expect(defaultConfig.quadrantChart!.chartWidth).toEqual(
|
||||
updatedConfig.quadrantChart!.chartWidth
|
||||
);
|
||||
});
|
||||
it('should set reset config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = { fontFamily: 'baf' };
|
||||
configApi.setConfig(config_1);
|
||||
const config_2 = configApi.getConfig();
|
||||
expect(config_2.fontFamily).toEqual(config_1.fontFamily);
|
||||
configApi.reset();
|
||||
const config_3 = configApi.getConfig();
|
||||
expect(config_3.fontFamily).toEqual(config_0.fontFamily);
|
||||
const config_4 = configApi.getSiteConfig();
|
||||
expect(config_4.fontFamily).toEqual(config_0.fontFamily);
|
||||
});
|
||||
it('should set global reset config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
expect(config_1.fontFamily).toEqual(config_0.fontFamily);
|
||||
const config_2 = configApi.getConfig();
|
||||
expect(config_2.fontFamily).toEqual(config_0.fontFamily);
|
||||
configApi.setConfig({ altFontFamily: 'bar-font' });
|
||||
const config_3 = configApi.getConfig();
|
||||
expect(config_3.altFontFamily).toEqual('bar-font');
|
||||
configApi.reset();
|
||||
const config_4 = configApi.getConfig();
|
||||
expect(config_4.altFontFamily).toBeUndefined();
|
||||
});
|
||||
});
|
@ -226,9 +226,11 @@ export const reset = (config = siteConfig): void => {
|
||||
updateCurrentConfig(config, directives);
|
||||
};
|
||||
|
||||
enum ConfigWarning {
|
||||
'LAZY_LOAD_DEPRECATED' = 'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
|
||||
}
|
||||
const ConfigWarning = {
|
||||
LAZY_LOAD_DEPRECATED:
|
||||
'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
|
||||
} as const;
|
||||
|
||||
type ConfigWarningStrings = keyof typeof ConfigWarning;
|
||||
const issuedWarnings: { [key in ConfigWarningStrings]?: boolean } = {};
|
||||
const issueWarning = (warning: ConfigWarningStrings) => {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -917,7 +917,9 @@ const class_box = (parent, node) => {
|
||||
((-1 * maxHeight) / 2 + verticalPos + lineHeight / 2) +
|
||||
')'
|
||||
);
|
||||
verticalPos += classTitleBBox.height + rowPadding;
|
||||
//get the height of the bounding box of each member if exists
|
||||
const memberBBox = lbl?.getBBox();
|
||||
verticalPos += (memberBBox?.height ?? 0) + rowPadding;
|
||||
});
|
||||
|
||||
verticalPos += lineHeight;
|
||||
@ -935,7 +937,8 @@ const class_box = (parent, node) => {
|
||||
'transform',
|
||||
'translate( ' + -maxWidth / 2 + ', ' + ((-1 * maxHeight) / 2 + verticalPos) + ')'
|
||||
);
|
||||
verticalPos += classTitleBBox.height + rowPadding;
|
||||
const memberBBox = lbl?.getBBox();
|
||||
verticalPos += (memberBBox?.height ?? 0) + rowPadding;
|
||||
});
|
||||
|
||||
rect
|
||||
|
@ -66,8 +66,11 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
||||
await Promise.all(
|
||||
[...images].map(
|
||||
(img) =>
|
||||
new Promise((res) =>
|
||||
img.addEventListener('load', function () {
|
||||
new Promise((res) => {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function setupImage() {
|
||||
img.style.display = 'flex';
|
||||
img.style.flexDirection = 'column';
|
||||
|
||||
@ -82,8 +85,15 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
||||
img.style.width = '100%';
|
||||
}
|
||||
res(img);
|
||||
})
|
||||
)
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (img.complete) {
|
||||
setupImage();
|
||||
}
|
||||
});
|
||||
img.addEventListener('error', setupImage);
|
||||
img.addEventListener('load', setupImage);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js';
|
||||
import flowchartElk from '../diagrams/flowchart/elk/detector.js';
|
||||
import timeline from '../diagrams/timeline/detector.js';
|
||||
import mindmap from '../diagrams/mindmap/detector.js';
|
||||
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||
import { registerDiagram } from './diagramAPI.js';
|
||||
|
||||
@ -79,6 +80,7 @@ export const addDiagrams = () => {
|
||||
stateV2,
|
||||
state,
|
||||
journey,
|
||||
quadrantChart
|
||||
quadrantChart,
|
||||
sankey
|
||||
);
|
||||
};
|
||||
|
135
packages/mermaid/src/diagrams/c4/parser/c4Container.spec.js
Normal file
135
packages/mermaid/src/diagrams/c4/parser/c4Container.spec.js
Normal file
@ -0,0 +1,135 @@
|
||||
import c4Db from '../c4Db.js';
|
||||
import c4 from './c4Diagram.jison';
|
||||
import { setConfig } from '../../../config.js';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe.each([
|
||||
['Container', 'container'],
|
||||
['ContainerDb', 'container_db'],
|
||||
['ContainerQueue', 'container_queue'],
|
||||
['Container_Ext', 'external_container'],
|
||||
['ContainerDb_Ext', 'external_container_db'],
|
||||
['ContainerQueue_Ext', 'external_container_queue'],
|
||||
])('parsing a C4 %s', function (macroName, elementName) {
|
||||
beforeEach(function () {
|
||||
c4.parser.yy = c4Db;
|
||||
c4.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse a C4 diagram with one Container correctly', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
title Container diagram for Internet Banking Container
|
||||
${macroName}(ContainerAA, "Internet Banking Container", "Technology", "Allows customers to view information about their bank accounts, and make payments.")`);
|
||||
|
||||
const yy = c4.parser.yy;
|
||||
|
||||
const shapes = yy.getC4ShapeArray();
|
||||
expect(shapes.length).toBe(1);
|
||||
const onlyShape = shapes[0];
|
||||
|
||||
expect(onlyShape).toEqual({
|
||||
alias: 'ContainerAA',
|
||||
descr: {
|
||||
text: 'Allows customers to view information about their bank accounts, and make payments.',
|
||||
},
|
||||
label: {
|
||||
text: 'Internet Banking Container',
|
||||
},
|
||||
link: undefined,
|
||||
sprite: undefined,
|
||||
tags: undefined,
|
||||
parentBoundary: 'global',
|
||||
typeC4Shape: {
|
||||
text: elementName,
|
||||
},
|
||||
techn: {
|
||||
text: 'Technology',
|
||||
},
|
||||
wrap: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse the alias', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, "Internet Banking Container")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
alias: 'ContainerAA',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse the label', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, "Internet Banking Container")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
label: {
|
||||
text: 'Internet Banking Container',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse the technology', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, "", "Java")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
techn: {
|
||||
text: 'Java',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse the description', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, "", "", "Allows customers to view information about their bank accounts, and make payments.")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
descr: {
|
||||
text: 'Allows customers to view information about their bank accounts, and make payments.',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse a sprite', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, $sprite="users")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
label: {
|
||||
text: {
|
||||
sprite: 'users',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse a link', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, $link="https://github.com/mermaidjs")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
label: {
|
||||
text: {
|
||||
link: 'https://github.com/mermaidjs',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse tags', function () {
|
||||
c4.parser.parse(`C4Context
|
||||
${macroName}(ContainerAA, $tags="tag1,tag2")`);
|
||||
|
||||
expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
|
||||
label: {
|
||||
text: {
|
||||
tags: 'tag1,tag2',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
@ -150,27 +150,27 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
"Node_R" { this.begin("node_r"); return 'NODE_R';}
|
||||
|
||||
|
||||
"Rel" { this.begin("rel"); return 'REL';}
|
||||
"BiRel" { this.begin("birel"); return 'BIREL';}
|
||||
"Rel_Up" { this.begin("rel_u"); return 'REL_U';}
|
||||
"Rel_U" { this.begin("rel_u"); return 'REL_U';}
|
||||
"Rel_Down" { this.begin("rel_d"); return 'REL_D';}
|
||||
"Rel_D" { this.begin("rel_d"); return 'REL_D';}
|
||||
"Rel_Left" { this.begin("rel_l"); return 'REL_L';}
|
||||
"Rel_L" { this.begin("rel_l"); return 'REL_L';}
|
||||
"Rel_Right" { this.begin("rel_r"); return 'REL_R';}
|
||||
"Rel_R" { this.begin("rel_r"); return 'REL_R';}
|
||||
"Rel_Back" { this.begin("rel_b"); return 'REL_B';}
|
||||
"RelIndex" { this.begin("rel_index"); return 'REL_INDEX';}
|
||||
"Rel" { this.begin("rel"); return 'REL';}
|
||||
"BiRel" { this.begin("birel"); return 'BIREL';}
|
||||
"Rel_Up" { this.begin("rel_u"); return 'REL_U';}
|
||||
"Rel_U" { this.begin("rel_u"); return 'REL_U';}
|
||||
"Rel_Down" { this.begin("rel_d"); return 'REL_D';}
|
||||
"Rel_D" { this.begin("rel_d"); return 'REL_D';}
|
||||
"Rel_Left" { this.begin("rel_l"); return 'REL_L';}
|
||||
"Rel_L" { this.begin("rel_l"); return 'REL_L';}
|
||||
"Rel_Right" { this.begin("rel_r"); return 'REL_R';}
|
||||
"Rel_R" { this.begin("rel_r"); return 'REL_R';}
|
||||
"Rel_Back" { this.begin("rel_b"); return 'REL_B';}
|
||||
"RelIndex" { this.begin("rel_index"); return 'REL_INDEX';}
|
||||
|
||||
"UpdateElementStyle" { this.begin("update_el_style"); return 'UPDATE_EL_STYLE';}
|
||||
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
|
||||
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
|
||||
"UpdateElementStyle" { this.begin("update_el_style"); return 'UPDATE_EL_STYLE';}
|
||||
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
|
||||
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
|
||||
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
|
||||
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
|
||||
|
||||
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
|
||||
<attribute>"," { }
|
||||
@ -189,7 +189,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
|
||||
'{' { /* this.begin("lbrace"); */ return "LBRACE";}
|
||||
'}' { /* this.popState(); */ return "RBRACE";}
|
||||
|
||||
|
||||
[\s]+ return 'SPACE';
|
||||
[\n\r]+ return 'EOL';
|
||||
<<EOF>> return 'EOF';
|
||||
@ -257,7 +257,7 @@ graphConfig
|
||||
statements
|
||||
: otherStatements
|
||||
| diagramStatements
|
||||
| otherStatements diagramStatements
|
||||
| otherStatements diagramStatements
|
||||
;
|
||||
|
||||
otherStatements
|
||||
@ -268,10 +268,10 @@ otherStatements
|
||||
|
||||
otherStatement
|
||||
: title {yy.setTitle($1.substring(6));$$=$1.substring(6);}
|
||||
| accDescription {yy.setAccDescription($1.substring(15));$$=$1.substring(15);}
|
||||
| accDescription {yy.setAccDescription($1.substring(15));$$=$1.substring(15);}
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
;
|
||||
|
||||
boundaryStatement
|
||||
@ -301,7 +301,7 @@ boundaryStopStatement
|
||||
diagramStatements
|
||||
: diagramStatement
|
||||
| diagramStatement NEWLINE
|
||||
| diagramStatement NEWLINE statements
|
||||
| diagramStatement NEWLINE statements
|
||||
;
|
||||
|
||||
diagramStatement
|
||||
@ -312,19 +312,19 @@ diagramStatement
|
||||
| SYSTEM_QUEUE attributes {yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT attributes {yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT_DB attributes {yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT_QUEUE attributes {yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
|
||||
| SYSTEM_EXT_QUEUE attributes {yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
|
||||
| CONTAINER attributes {yy.addContainer('container', ...$2); $$=$2;}
|
||||
| CONTAINER_DB attributes {yy.addContainer('container_db', ...$2); $$=$2;}
|
||||
| CONTAINER_QUEUE attributes {yy.addContainer('container_queue', ...$2); $$=$2;}
|
||||
| CONTAINER_EXT attributes {yy.addContainer('external_container', ...$2); $$=$2;}
|
||||
| CONTAINER_EXT_DB attributes {yy.addContainer('external_container_db', ...$2); $$=$2;}
|
||||
| CONTAINER_EXT_QUEUE attributes {yy.addContainer('external_container_queue', ...$2); $$=$2;}
|
||||
| CONTAINER_EXT_QUEUE attributes {yy.addContainer('external_container_queue', ...$2); $$=$2;}
|
||||
| COMPONENT attributes {yy.addComponent('component', ...$2); $$=$2;}
|
||||
| COMPONENT_DB attributes {yy.addComponent('component_db', ...$2); $$=$2;}
|
||||
| COMPONENT_QUEUE attributes {yy.addComponent('component_queue', ...$2); $$=$2;}
|
||||
| COMPONENT_EXT attributes {yy.addComponent('external_component', ...$2); $$=$2;}
|
||||
| COMPONENT_EXT_DB attributes {yy.addComponent('external_component_db', ...$2); $$=$2;}
|
||||
| COMPONENT_EXT_QUEUE attributes {yy.addComponent('external_component_queue', ...$2); $$=$2;}
|
||||
| COMPONENT_EXT_QUEUE attributes {yy.addComponent('external_component_queue', ...$2); $$=$2;}
|
||||
| boundaryStatement
|
||||
| REL attributes {yy.addRel('rel', ...$2); $$=$2;}
|
||||
| BIREL attributes {yy.addRel('birel', ...$2); $$=$2;}
|
||||
|
@ -264,6 +264,113 @@ class C13["With Città foreign language"]
|
||||
const str = 'classDiagram\n' + 'note "test"\n';
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
const keywords = [
|
||||
'direction',
|
||||
'classDiagram',
|
||||
'classDiagram-v2',
|
||||
'namespace',
|
||||
'{}',
|
||||
'{',
|
||||
'}',
|
||||
'()',
|
||||
'(',
|
||||
')',
|
||||
'[]',
|
||||
'[',
|
||||
']',
|
||||
'class',
|
||||
'\n',
|
||||
'cssClass',
|
||||
'callback',
|
||||
'link',
|
||||
'click',
|
||||
'note',
|
||||
'note for',
|
||||
'<<',
|
||||
'>>',
|
||||
'call ',
|
||||
'~',
|
||||
'~Generic~',
|
||||
'_self',
|
||||
'_blank',
|
||||
'_parent',
|
||||
'_top',
|
||||
'<|',
|
||||
'|>',
|
||||
'>',
|
||||
'<',
|
||||
'*',
|
||||
'o',
|
||||
'\\',
|
||||
'--',
|
||||
'..',
|
||||
'-->',
|
||||
'--|>',
|
||||
': label',
|
||||
':::',
|
||||
'.',
|
||||
'+',
|
||||
'alphaNum',
|
||||
'!',
|
||||
'0123',
|
||||
'function()',
|
||||
'function(arg1, arg2)',
|
||||
];
|
||||
|
||||
it.each(keywords)('should handle a note with %s in it', function (keyword: string) {
|
||||
const str = `classDiagram
|
||||
note "This is a keyword: ${keyword}. It truly is."
|
||||
`;
|
||||
parser.parse(str);
|
||||
expect(classDb.getNotes()[0].text).toEqual(`This is a keyword: ${keyword}. It truly is.`);
|
||||
});
|
||||
|
||||
it.each(keywords)(
|
||||
'should handle note with %s at beginning of string',
|
||||
function (keyword: string) {
|
||||
const str = `classDiagram
|
||||
note "${keyword}"`;
|
||||
|
||||
parser.parse(str);
|
||||
expect(classDb.getNotes()[0].text).toEqual(`${keyword}`);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(keywords)('should handle a "note for" with a %s in it', function (keyword: string) {
|
||||
const str = `classDiagram
|
||||
class Something {
|
||||
int id
|
||||
string name
|
||||
}
|
||||
note for Something "This is a keyword: ${keyword}. It truly is."
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
expect(classDb.getNotes()[0].text).toEqual(`This is a keyword: ${keyword}. It truly is.`);
|
||||
});
|
||||
|
||||
it.each(keywords)(
|
||||
'should handle a "note for" with a %s at beginning of string',
|
||||
function (keyword: string) {
|
||||
const str = `classDiagram
|
||||
class Something {
|
||||
int id
|
||||
string name
|
||||
}
|
||||
note for Something "${keyword}"
|
||||
`;
|
||||
|
||||
parser.parse(str);
|
||||
expect(classDb.getNotes()[0].text).toEqual(`${keyword}`);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(keywords)('should elicit error for %s after NOTE token', function (keyword: string) {
|
||||
const str = `classDiagram
|
||||
note ${keyword}`;
|
||||
expect(() => parser.parse(str)).toThrowError(/(Expecting\s'STR'|Unrecognized\stext)/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing class defined in brackets', function () {
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
// @ts-ignore - no types
|
||||
import { LALRGenerator } from 'jison';
|
||||
|
||||
const getAbsolutePath = (relativePath: string) => {
|
||||
return fileURLToPath(new URL(relativePath, import.meta.url));
|
||||
};
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
it('should have no conflicts', async function () {
|
||||
const grammarSource = await readFile(getAbsolutePath('./parser/classDiagram.jison'), 'utf8');
|
||||
const grammarParser = new LALRGenerator(grammarSource, {});
|
||||
expect(grammarParser.conflicts).toBe(0);
|
||||
});
|
||||
});
|
@ -141,8 +141,6 @@ const insertMarkers = function (elem) {
|
||||
export const draw = function (text, id, _version, diagObj) {
|
||||
const conf = getConfig().class;
|
||||
idCache = {};
|
||||
// diagObj.db.clear();
|
||||
// diagObj.parser.parse(text);
|
||||
|
||||
log.info('Rendering diagram ' + text);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user