Compare commits

..

7 Commits

Author SHA1 Message Date
Alois Klink
85ec96a72d chore: bump mermaid version to v10.9.3
Updates the bundled version of dependencies in the following files:

- `dist/mermaid.min.js`
- `dist/mermaid.js`
- `dist/mermaid.esm.mjs`
- `dist/mermaid.esm.min.mjs`

**If you are not using these files
(e.g. you are using the default NPM export of `mermaid`,
 e.g. `import mermaid from 'mermaid'`,
 or you are using `dist/mermaid.core.mjs`),
this release is identical to v10.9.2.**

This is to avoid potential security issues in KaTeX and DOMPurify, see:

- https://github.com/cure53/DOMPurify/security/advisories/GHSA-mmhx-hmjr-r674
- https://github.com/advisories/GHSA-64fm-8hw2-v72w
- https://github.com/advisories/GHSA-cvr6-37gx-v8wc
- https://github.com/advisories/GHSA-f98w-7cxr-ff2h
- https://github.com/advisories/GHSA-3wc5-fcw2-2329

These dependencies have already been updated in
[v11.0.0](https://github.com/mermaid-js/mermaid/releases/tag/v11.0.0).

Changelog
=========

Chore
-----

- Updates the bundled version of KaTeX to 0.16.11
  (2bedd0ef87)
- Updates the bundled version of DOMPurify to 3.1.6
  (92a07ffe40)
2024-10-22 17:32:05 +09:00
Alois Klink
9301a5788a style: prettify src/diagrams/block/blockDB.ts 2024-10-10 17:44:54 +09:00
Alois Klink
2bedd0ef87 chore(deps): update katex to 0.16.11
This affects the built:

- `dist/mermaid.min.js`
- `dist/mermaid.js`
- `dist/mermaid.esm.mjs`
- `dist/mermaid.esm.min.mjs`

See: GHSA-64fm-8hw2-v72w
See: GHSA-cvr6-37gx-v8wc
See: GHSA-f98w-7cxr-ff2h
See: GHSA-3wc5-fcw2-2329
2024-10-10 17:06:09 +09:00
Alois Klink
92a07ffe40 chore(deps): update bundled DOMPurify to 3.1.6
This affects the built:

- `dist/mermaid.min.js`
- `dist/mermaid.js`
- `dist/mermaid.esm.mjs`
- `dist/mermaid.esm.min.mjs`

See: GHSA-mmhx-hmjr-r674
2024-10-10 16:53:45 +09:00
Sidharth Vinod
4dd4997c4d chore: Bump version 2024-10-02 15:01:48 +05:30
Jiahao Guo
fc61512a0a [10] ci: upgrade to pnpm/action-setup@v4 to avoid CI failures
see: https://github.com/pnpm/action-setup/issues/135

(cherry picked from commit fde668a3a6)
2024-10-02 14:52:25 +09:00
Alois Klink
402abdf883 [10] fix: ban version v3.1.7 of DOMPurify
[DOMPurify v3.1.7][1] forbids the use of `<foreignElement>` for HTML
inside of an `<svg>` element, which breaks many mermaid diagrams.

It is likely that v3.1.8 will add a new option that will allow us to
re-enable this behaviour, but v3.1.7 definitely does not work.

(cherry picked from commit de2c05cd54)

[1]: https://github.com/cure53/DOMPurify/releases/tag/3.1.7

See: https://github.com/cure53/DOMPurify/issues/1002
Fix: https://github.com/mermaid-js/mermaid/issues/5904
2024-10-02 14:30:19 +09:00
703 changed files with 26496 additions and 61788 deletions

View File

@@ -1,36 +0,0 @@
export interface PackageOptions {
name: string;
packageName: string;
file: string;
}
/**
* Shared common options for both ESBuild and Vite
*/
export const packageOptions = {
parser: {
name: 'mermaid-parser',
packageName: 'parser',
file: 'index.ts',
},
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
'mermaid-layout-elk': {
name: 'mermaid-layout-elk',
packageName: 'mermaid-layout-elk',
file: 'layouts.ts',
},
} as const satisfies Record<string, PackageOptions>;

View File

@@ -1,5 +0,0 @@
import { generate } from 'langium-cli';
export async function generateLangium() {
await generate({ file: `./packages/parser/langium-config.json` });
}

View File

@@ -1,126 +0,0 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
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',
'xyChart',
'requirement',
'mindmap',
'kanban',
'timeline',
'gitGraph',
'c4',
'sankey',
'block',
'packet',
'architecture',
] 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;
}
export const loadSchema = (src: string, filename: string): JSONSchemaType<MermaidConfig> => {
const jsonSchema = load(src, {
filename,
// 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 jsonSchema;
};
export const getDefaults = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(generateDefaults(schema), undefined, 2)};`;
};
export const getSchema = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(schema, undefined, 2)};`;
};

View File

@@ -1,25 +0,0 @@
/* eslint-disable no-console */
import { packageOptions } from './common.js';
import { execSync } from 'child_process';
const buildType = (packageName: string) => {
console.log(`Building types for ${packageName}`);
try {
const out = execSync(`tsc -p ./packages/${packageName}/tsconfig.json --emitDeclarationOnly`);
if (out.length > 0) {
console.log(out.toString());
}
} catch (e) {
console.error(e);
if (e.stdout.length > 0) {
console.error(e.stdout.toString());
}
if (e.stderr.length > 0) {
console.error(e.stderr.toString());
}
}
};
for (const { packageName } of Object.values(packageOptions)) {
buildType(packageName);
}

View File

@@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@@ -1,12 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "mermaid-js/mermaid" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"bumpVersionsWithWorkspaceProtocolOnly": true,
"ignore": ["@mermaid-js/docs", "@mermaid-js/webpack-test", "@mermaid-js/mermaid-example-diagram"]
}

3
.commitlintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": ["@commitlint/config-conventional"]
}

View File

@@ -13,7 +13,6 @@ bqstring
BQUOTE BQUOTE
bramp bramp
BRKT BRKT
brotli
callbackargs callbackargs
callbackname callbackname
classdef classdef
@@ -26,10 +25,8 @@ concat
controlx controlx
controly controly
CSSCLASS CSSCLASS
curv
CYLINDEREND CYLINDEREND
CYLINDERSTART CYLINDERSTART
DAGA
datakey datakey
DEND DEND
descr descr
@@ -56,8 +53,6 @@ GENERICTYPE
getBoundarys getBoundarys
grammr grammr
graphtype graphtype
halign
iife
interp interp
introdcued introdcued
INVTRAPEND INVTRAPEND
@@ -68,7 +63,6 @@ Kaufmann
keyify keyify
LABELPOS LABELPOS
LABELTYPE LABELTYPE
layoutstop
lcov lcov
LEFTOF LEFTOF
Lexa Lexa
@@ -80,13 +74,11 @@ loglevel
LOGMSG LOGMSG
lookaheads lookaheads
mdast mdast
metafile
minlen minlen
Mstartx Mstartx
MULT MULT
NODIR NODIR
NSTR NSTR
outdir
Qcontrolx Qcontrolx
reinit reinit
rels rels
@@ -94,7 +86,6 @@ reqs
rewritelinks rewritelinks
rgba rgba
RIGHTOF RIGHTOF
roughjs
sankey sankey
sequencenumber sequencenumber
shrc shrc
@@ -114,17 +105,13 @@ strikethrough
stringifying stringifying
struct struct
STYLECLASS STYLECLASS
STYLEDEF
STYLEOPTS STYLEOPTS
subcomponent subcomponent
subcomponents subcomponents
subconfig
SUBROUTINEEND SUBROUTINEEND
SUBROUTINESTART SUBROUTINESTART
Subschemas Subschemas
substr substr
SVGG
SVGSVG
TAGEND TAGEND
TAGSTART TAGSTART
techn techn
@@ -135,13 +122,11 @@ titlevalue
topbar topbar
TRAPEND TRAPEND
TRAPSTART TRAPSTART
treemap
ts-nocheck ts-nocheck
tsdoc tsdoc
typeof typeof
typestr typestr
unshift unshift
urlsafe
verifymethod verifymethod
VERIFYMTHD VERIFYMTHD
WARN_DOCSDIR_DOESNT_MATCH WARN_DOCSDIR_DOESNT_MATCH

View File

@@ -4,6 +4,5 @@ cpettitt
Dong Cai Dong Cai
Nikolay Rozhkov Nikolay Rozhkov
Peng Xiao Peng Xiao
Per Brolin
subhash-halder subhash-halder
Vinod Sidharth Vinod Sidharth

View File

@@ -28,9 +28,6 @@ dictionaryDefinitions:
- name: suggestions - name: suggestions
words: words:
- none - none
- disp
- subproc
- tria
suggestWords: suggestWords:
- seperator:separator - seperator:separator
- vertice:vertex - vertice:vertex

View File

@@ -20,17 +20,14 @@ dagre-d3
Deepdwn Deepdwn
Docsify Docsify
Docsy Docsy
Doctave
DokuWiki DokuWiki
dompurify dompurify
elkjs elkjs
fcose
fontawesome fontawesome
Foswiki Foswiki
Gitea Gitea
graphlib graphlib
Grav Grav
icones
iconify iconify
Inkdrop Inkdrop
jiti jiti
@@ -57,17 +54,13 @@ presetAttributify
pyplot pyplot
redmine redmine
rehype rehype
roughjs
rscratch rscratch
shiki
Slidev
sparkline sparkline
sphinxcontrib sphinxcontrib
ssim ssim
stylis stylis
Swimm Swimm
tsbuildinfo tsbuildinfo
tseslint
Tuleap Tuleap
Typora Typora
unocss unocss

View File

@@ -1,18 +1,14 @@
Adamiecki Adamiecki
arrowend arrowend
Bendpoints
bmatrix bmatrix
braintree braintree
catmull catmull
compositTitleSize compositTitleSize
curv
doublecircle doublecircle
elems elems
gantt gantt
gitgraph gitgraph
gzipped gzipped
handDrawn
kanban
knsv knsv
Knut Knut
marginx marginx
@@ -21,12 +17,10 @@ Markdownish
mermaidjs mermaidjs
mindmap mindmap
mindmaps mindmaps
mrtree
multigraph multigraph
nodesep nodesep
NOTEGROUP NOTEGROUP
Pinterest Pinterest
procs
rankdir rankdir
ranksep ranksep
rect rect

View File

@@ -1,7 +1 @@
BRANDES
circo
handDrawn
KOEPF
neato
newbranch newbranch
validify

View File

@@ -1,70 +0,0 @@
import { build } from 'esbuild';
import { mkdir, writeFile } from 'node:fs/promises';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
import type { MermaidBuildOptions } from './util.js';
import { defaultOptions, getBuildConfig } from './util.js';
const shouldVisualize = process.argv.includes('--visualize');
const buildPackage = async (entryName: keyof typeof packageOptions) => {
const commonOptions: MermaidBuildOptions = {
...defaultOptions,
options: packageOptions[entryName],
} as const;
const buildConfigs: MermaidBuildOptions[] = [
// package.mjs
{ ...commonOptions },
// package.min.mjs
{
...commonOptions,
minify: true,
metafile: shouldVisualize,
},
// package.core.mjs
{ ...commonOptions, core: true },
];
if (entryName === 'mermaid') {
const iifeOptions: MermaidBuildOptions = { ...commonOptions, format: 'iife' };
buildConfigs.push(
// mermaid.js
{ ...iifeOptions },
// mermaid.min.js
{ ...iifeOptions, minify: true, metafile: shouldVisualize }
);
}
const results = await Promise.all(buildConfigs.map((option) => build(getBuildConfig(option))));
if (shouldVisualize) {
for (const { metafile } of results) {
if (!metafile?.outputs) {
continue;
}
const fileName = Object.keys(metafile.outputs)
.find((file) => !file.includes('chunks') && file.endsWith('js'))!
.replace('dist/', '');
// Upload metafile into https://esbuild.github.io/analyze/
await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile));
}
}
};
const handler = (e) => {
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
};
const main = async () => {
await generateLangium();
await mkdir('stats', { recursive: true });
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
// it should build `parser` before `mermaid` because it's a dependency
for (const pkg of packageNames) {
await buildPackage(pkg).catch(handler);
}
};
void main();

View File

@@ -1,15 +0,0 @@
import { readFile } from 'node:fs/promises';
import { transformJison } from '../.build/jisonTransformer.js';
import type { Plugin } from 'esbuild';
export const jisonPlugin: Plugin = {
name: 'jison',
setup(build) {
build.onLoad({ filter: /\.jison$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const contents = transformJison(source);
return { contents, warnings: [] };
});
},
};

View File

@@ -1,35 +0,0 @@
import type { JSONSchemaType } from 'ajv/dist/2019.js';
import type { MermaidConfig } from '../packages/mermaid/src/config.type.js';
import { readFile } from 'node:fs/promises';
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';
/**
* ESBuild 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 const jsonSchemaPlugin = {
name: 'json-schema-plugin',
setup(build) {
let schema: JSONSchemaType<MermaidConfig> | undefined = undefined;
let content = '';
build.onLoad({ filter: /config\.schema\.yaml$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const resolvedSchema: JSONSchemaType<MermaidConfig> =
content === source && schema ? schema : loadSchema(source, args.path);
if (content !== source) {
content = source;
schema = resolvedSchema;
}
const contents = args.suffix.includes('only-defaults')
? getDefaults(resolvedSchema)
: getSchema(resolvedSchema);
return { contents, warnings: [] };
});
},
};
export default jsonSchemaPlugin;

View File

@@ -1,122 +0,0 @@
/* eslint-disable no-console */
import chokidar from 'chokidar';
import cors from 'cors';
import { context } from 'esbuild';
import type { Request, Response } from 'express';
import express from 'express';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
import { defaultOptions, getBuildConfig } from './util.js';
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({
...defaultOptions,
minify: false,
core: false,
options: packageOptions[packageName],
})
);
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: false,
core: false,
options: packageOptions.mermaid,
format: 'iife',
});
configs.push(mermaidIIFEConfig);
const contexts = await Promise.all(
configs.map(async (config) => ({ config, context: await context(config) }))
);
let rebuildCounter = 1;
const rebuildAll = async () => {
const buildNumber = rebuildCounter++;
const timeLabel = `Rebuild ${buildNumber} Time (total)`;
console.time(timeLabel);
await Promise.all(
contexts.map(async ({ config, context }) => {
const buildVariant = `Rebuild ${buildNumber} Time (${Object.keys(config.entryPoints!)[0]} ${config.format})`;
console.time(buildVariant);
await context.rebuild();
console.timeEnd(buildVariant);
})
).catch((e) => console.error(e));
console.timeEnd(timeLabel);
};
let clients: { id: number; response: Response }[] = [];
function eventsHandler(request: Request, response: Response) {
const headers = {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
};
response.writeHead(200, headers);
const clientId = Date.now();
clients.push({
id: clientId,
response,
});
request.on('close', () => {
clients = clients.filter((client) => client.id !== clientId);
});
}
let timeoutID: NodeJS.Timeout | undefined = undefined;
/**
* Debounce file change events to avoid rebuilding multiple times.
*/
function handleFileChange() {
if (timeoutID !== undefined) {
clearTimeout(timeoutID);
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
timeoutID = setTimeout(async () => {
await rebuildAll();
sendEventsToAll();
timeoutID = undefined;
}, 100);
}
function sendEventsToAll() {
clients.forEach(({ response }) => response.write(`data: ${Date.now()}\n\n`));
}
async function createServer() {
await generateLangium();
handleFileChange();
const app = express();
chokidar
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
ignoreInitial: true,
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
})
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.on('all', async (event, path) => {
// Ignore other events.
if (!['add', 'change'].includes(event)) {
return;
}
console.log(`${path} changed. Rebuilding...`);
if (path.endsWith('.langium')) {
await generateLangium();
}
handleFileChange();
});
app.use(cors());
app.get('/events', eventsHandler);
for (const { packageName } of Object.values(packageOptions)) {
app.use(express.static(`./packages/${packageName}/dist`));
}
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));
app.listen(9000, () => {
console.log(`Listening on http://localhost:9000`);
});
}
void createServer();

View File

@@ -1,106 +0,0 @@
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import type { BuildOptions } from 'esbuild';
import { readFileSync } from 'fs';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import type { PackageOptions } from '../.build/common.js';
import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export interface MermaidBuildOptions extends BuildOptions {
minify: boolean;
core: boolean;
metafile: boolean;
format: 'esm' | 'iife';
options: PackageOptions;
}
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'> = {
minify: false,
metafile: false,
core: false,
format: 'esm',
} as const;
const buildOptions = (override: BuildOptions): BuildOptions => {
return {
bundle: true,
minify: true,
keepNames: true,
platform: 'browser',
tsconfig: 'tsconfig.json',
resolveExtensions: ['.ts', '.js', '.json', '.jison', '.yaml'],
external: ['require', 'fs', 'path'],
outdir: 'dist',
plugins: [jisonPlugin, jsonSchemaPlugin],
sourcemap: 'external',
...override,
};
};
const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOptions) => {
if (core) {
fileName += '.core';
} else if (format === 'esm') {
fileName += '.esm';
}
if (minify) {
fileName += '.min';
}
return fileName;
};
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
const {
core,
metafile,
format,
minify,
options: { name, file, packageName },
} = options;
const external: string[] = ['require', 'fs', 'path'];
const outFileName = getFileName(name, options);
const output: BuildOptions = buildOptions({
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
entryPoints: {
[outFileName]: `src/${file}`,
},
metafile,
minify,
logLevel: 'info',
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
define: {
'import.meta.vitest': 'undefined',
},
});
if (core) {
const { dependencies } = JSON.parse(
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
);
// Core build is used to generate file without bundled dependencies.
// This is used by downstream projects to bundle dependencies themselves.
// Ignore dependencies and any dependencies of dependencies
external.push(...Object.keys(dependencies));
output.external = external;
}
if (format === 'iife') {
output.format = 'iife';
output.splitting = false;
output.globalName = '__esbuild_esm_mermaid';
// Workaround for removing the .default access in esbuild IIFE.
// https://github.com/mermaid-js/mermaid/pull/4109#discussion_r1292317396
output.footer = {
js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;',
};
output.outExtension = { '.js': '.js' };
} else {
output.format = 'esm';
output.splitting = true;
output.outExtension = { '.js': '.mjs' };
}
return output;
};

8
.eslintignore Normal file
View File

@@ -0,0 +1,8 @@
dist/**
.github/**
docs/Setup.md
cypress.config.js
cypress/plugins/index.js
coverage
*.json
node_modules

189
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,189 @@
module.exports = {
env: {
browser: true,
es6: true,
'jest/globals': true,
node: true,
},
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 2020,
allowAutomaticSingleRunInference: true,
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
parser: '@typescript-eslint/parser',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:json/recommended',
'plugin:markdown/recommended',
'plugin:@cspell/recommended',
'prettier',
],
plugins: [
'@typescript-eslint',
'no-only-tests',
'html',
'jest',
'jsdoc',
'json',
'@cspell',
'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',
'no-prototype-builtins': 'off',
'no-unused-vars': 'off',
'cypress/no-async-tests': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description',
'ts-check': 'allow-with-description',
minimumDescriptionLength: 10,
},
],
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'typeLike',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: false,
},
},
],
'json/*': ['error', 'allowComments'],
'@cspell/spellchecker': [
'error',
{
checkIdentifiers: true,
checkStrings: true,
checkStringTemplates: true,
},
],
'no-empty': [
'error',
{
allowEmptyCatch: true,
},
],
'no-only-tests/no-only-tests': 'error',
'lodash/import-scope': ['error', 'method'],
'unicorn/better-regex': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-array-push-push': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-instanceof-array': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unsafe-regex': 'warn',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-object-from-entries': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
'unicorn/string-content': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/no-lonely-if': 'error',
},
overrides: [
{
files: ['cypress/**', 'demos/**'],
rules: {
'no-console': 'off',
},
},
{
files: ['*.{js,jsx,mjs,cjs}'],
extends: ['plugin:jsdoc/recommended'],
rules: {
'jsdoc/check-indentation': 'off',
'jsdoc/check-alignment': 'off',
'jsdoc/check-line-alignment': 'off',
'jsdoc/multiline-blocks': 'off',
'jsdoc/newline-after-description': 'off',
'jsdoc/tag-lines': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-param-type': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/require-returns-description': 'off',
},
},
{
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',
},
},
{
files: ['*.spec.{ts,js}', 'cypress/**', 'demos/**', '**/docs/**'],
rules: {
'jsdoc/require-jsdoc': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
{
files: ['*.spec.{ts,js}', 'tests/**', 'cypress/**/*.js'],
rules: {
'@cspell/spellchecker': [
'error',
{
checkIdentifiers: false,
checkStrings: false,
checkStringTemplates: false,
},
],
},
},
{
files: ['*.html', '*.md', '**/*.md/*'],
rules: {
'no-var': 'error',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
parserOptions: {
project: null,
},
},
],
};

View File

@@ -15,4 +15,3 @@ coverage:
# Turing off for now as code coverage isn't stable and causes unnecessary build failures. # Turing off for now as code coverage isn't stable and causes unnecessary build failures.
# default: # default:
# threshold: 2% # threshold: 2%
patch: off

8
.github/lychee.toml vendored
View File

@@ -41,13 +41,7 @@ exclude = [
"https://bundlephobia.com", "https://bundlephobia.com",
# Chrome webstore migration issue. Temporary # Chrome webstore migration issue. Temporary
"https://chromewebstore.google.com", "https://chromewebstore.google.com"
# Drupal 403
"https://(www.)?drupal.org",
# Swimm returns 404, eventhough the link is valid
"https://docs.swimm.io"
] ]
# Exclude all private IPs from checking. # Exclude all private IPs from checking.

View File

@@ -15,4 +15,4 @@ Make sure you
- [ ] :book: have read the [contribution guidelines](https://mermaid.js.org/community/contributing.html) - [ ] :book: have read the [contribution guidelines](https://mermaid.js.org/community/contributing.html)
- [ ] :computer: have added necessary unit/e2e tests. - [ ] :computer: have added necessary unit/e2e tests.
- [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://mermaid.js.org/community/contributing.html#update-documentation) is used for all new features. - [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://mermaid.js.org/community/contributing.html#update-documentation) is used for all new features.
- [ ] :butterfly: If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`. - [ ] :bookmark: targeted `develop` branch

36
.github/release-drafter.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name-template: '$NEXT_PATCH_VERSION'
tag-template: '$NEXT_PATCH_VERSION'
categories:
- title: '🚨 **Breaking Changes**'
labels:
- 'Breaking Change'
- title: '🚀 Features'
labels:
- 'Type: Enhancement'
- 'feature' # deprecated, new PRs shouldn't have this
- title: '🐛 Bug Fixes'
labels:
- 'Type: Bug / Error'
- 'fix' # deprecated, new PRs shouldn't have this
- title: '🧰 Maintenance'
labels:
- 'Type: Other'
- 'chore' # deprecated, new PRs shouldn't have this
- title: '⚡️ Performance'
labels:
- 'Type: Performance'
- title: '📚 Documentation'
labels:
- 'Area: Documentation'
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
sort-by: title
sort-direction: ascending
exclude-labels:
- 'Skip changelog'
no-changes-template: 'This release contains minor changes and bugfixes.'
template: |
# Release Notes
$CHANGES
🎉 **Thanks to all contributors helping with this release!** 🎉

View File

@@ -1,45 +0,0 @@
name: autofix.ci # needed to securely identify the workflow
on:
pull_request:
branches-ignore:
- 'renovate/**'
permissions:
contents: read
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
autofix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: |
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Fix Linting
shell: bash
run: pnpm -w run lint:fix
- name: Sync `./src/config.type.ts` with `./src/schemas/config.schema.yaml`
shell: bash
run: pnpm run --filter mermaid types:build-config
- name: Build Docs
working-directory: ./packages/mermaid
run: pnpm run docs:build
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c # main

View File

@@ -8,8 +8,6 @@ on:
pull_request: pull_request:
merge_group: merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: permissions:
contents: read contents: read
@@ -18,12 +16,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
cache: pnpm cache: pnpm
node-version-file: '.node-version' node-version-file: '.node-version'

49
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Build
on:
push: {}
merge_group:
pull_request:
types:
- opened
- synchronize
- ready_for_review
permissions:
contents: read
jobs:
build-mermaid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: |
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Build
run: pnpm run build
- name: Upload Mermaid Build as Artifact
uses: actions/upload-artifact@v3
with:
name: mermaid-build
path: packages/mermaid/dist
- name: Upload Mermaid Mindmap Build as Artifact
uses: actions/upload-artifact@v3
with:
name: mermaid-mindmap-build
path: packages/mermaid-mindmap/dist

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
- name: Check for difference in README.md and docs/README.md - name: Check for difference in README.md and docs/README.md
run: | run: |

26
.github/workflows/checks.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
on:
push:
merge_group:
pull_request:
types:
- opened
- synchronize
- ready_for_review
name: Static analysis on Test files
jobs:
check-tests:
runs-on: ubuntu-latest
name: check tests
if: github.repository_owner == 'mermaid-js'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: testomatio/check-tests@stable
with:
framework: cypress
tests: './cypress/e2e/**/**.spec.js'
token: ${{ secrets.GITHUB_TOKEN }}
has-tests-label: true

View File

@@ -11,9 +11,6 @@ on:
- synchronize - synchronize
- ready_for_review - ready_for_review
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@@ -32,11 +29,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/init@v2
with: with:
config-file: ./.github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
@@ -48,7 +45,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -62,4 +59,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 uses: github/codeql-action/analyze@v2

View File

@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 uses: actions/dependency-review-action@v3

View File

@@ -11,8 +11,6 @@ on:
default: master default: master
description: 'Parent branch to use for PRs' description: 'Parent branch to use for PRs'
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: permissions:
contents: read contents: read
@@ -32,13 +30,13 @@ jobs:
run: | run: |
echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run." echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json # uses version from "packageManager" field in package.json
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
@@ -54,7 +52,7 @@ jobs:
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com' APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@d79d2d530a66e641eb4a5f227e13bc985c60b964 # v4.2.2 uses: cypress-io/github-action@v4
id: cypress id: cypress
with: with:
start: pnpm run dev start: pnpm run dev

View File

@@ -1,53 +0,0 @@
name: E2E - Generate Timings
on:
# run this workflow every night at 3am
schedule:
- cron: '28 3 * * *'
# or when the user triggers it from GitHub Actions page
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
timings:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
id: cypress
with:
install: false
start: pnpm run dev:coverage
wait-on: 'http://localhost:9000'
browser: chrome
publish-summary: false
env:
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
SPLIT: 1
SPLIT_INDEX: 0
SPLIT_FILE: 'cypress/timings.json'
- name: Commit changes
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
add: 'cypress/timings.json'
author_name: 'github-actions[bot]'
author_email: '41898282+github-actions[bot]@users.noreply.github.com'
message: 'chore: update E2E timings'

View File

@@ -1,33 +1,25 @@
# We use github cache to save snapshots between runs.
# For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used.
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
# These are then downloaded before running the E2E, providing the reference snapshots.
# If there are any errors, the diff image is uploaded to artifacts, and the user is notified.
name: E2E name: E2E
on: on:
push: push:
branches: branches-ignore:
- develop - 'gh-readonly-queue/**'
- master
- release/**
pull_request: pull_request:
merge_group: merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: permissions:
contents: read contents: read
env: env:
# For PRs and MergeQueues, the target commit is used, and for push events to non-develop branches, github.event.previous is used if available. Otherwise, 'develop' is used. # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used.
targetHash: >- targetHash: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || (github.event.before == '0000000000000000000000000000000000000000' && 'develop' || github.event.before) }}
${{
github.event.pull_request.base.sha ||
github.event.merge_group.base_sha ||
(
(
(github.event_name == 'push' && github.ref == 'refs/heads/develop') ||
github.event.before == '0000000000000000000000000000000000000000'
) && 'develop'
) ||
github.event.before
}}
jobs: jobs:
cache: cache:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -35,15 +27,15 @@ jobs:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1 image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001 options: --user 1001
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
- name: Cache snapshots - name: Cache snapshots
id: cache-snapshot id: cache-snapshot
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache@v4
with: with:
save-always: true save-always: true
path: ./cypress/snapshots path: ./cypress/snapshots
@@ -52,23 +44,18 @@ jobs:
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots. # If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
- name: Switch to base branch - name: Switch to base branch
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
with: with:
ref: ${{ env.targetHash }} ref: ${{ env.targetHash }}
- name: Install dependencies - name: Cypress run
uses: cypress-io/github-action@v4
id: cypress-snapshot-gen
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with: with:
# just perform install start: pnpm run dev
runTests: false wait-on: 'http://localhost:9000'
browser: chrome
- name: Calculate bundle size
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true'}}
run: |
pnpm run build:viz
mkdir -p cypress/snapshots/stats/base
mv stats cypress/snapshots/stats/base
e2e: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -79,67 +66,49 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
containers: [1, 2, 3, 4, 5] containers: [1, 2, 3, 4]
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json # uses version from "packageManager" field in package.json
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
node-version-file: '.node-version' node-version-file: '.node-version'
# These cached snapshots are downloaded, providing the reference snapshots. # These cached snapshots are downloaded, providing the reference snapshots.
- name: Cache snapshots - name: Cache snapshots
id: cache-snapshot id: cache-snapshot
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache/restore@v3
with: with:
path: ./cypress/snapshots path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }} key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
- name: Output size diff
if: ${{ matrix.containers == 1 }}
run: |
pnpm run build:viz
mv stats cypress/snapshots/stats/head
echo '## Bundle size difference' >> "$GITHUB_STEP_SUMMARY"
echo '' >> "$GITHUB_STEP_SUMMARY"
npx tsx scripts/size.ts >> "$GITHUB_STEP_SUMMARY"
# Install NPM dependencies, cache them correctly # Install NPM dependencies, cache them correctly
# and run all Cypress tests # and run all Cypress tests
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 uses: cypress-io/github-action@v4
id: cypress id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
# Otherwise (e.g. if running from fork), we run on a single container only
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with: with:
install: false
start: pnpm run dev:coverage start: pnpm run dev:coverage
wait-on: 'http://localhost:9000' wait-on: 'http://localhost:9000'
browser: chrome browser: chrome
# Disable recording if we don't have an API key # Disable recording if we don't have an API key
# e.g. if this action was run from a fork # e.g. if this action was run from a fork
record: ${{ secrets.CYPRESS_RECORD_KEY != '' }} record: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
parallel: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
env: env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
VITEST_COVERAGE: true VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }} CYPRESS_COMMIT: ${{ github.sha }}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL: true
ARGOS_PARALLEL_TOTAL: ${{ strategy.job-total }}
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
SPLIT: ${{ strategy.job-total }}
SPLIT_INDEX: ${{ strategy.job-index }}
SPLIT_FILE: 'cypress/timings.json'
- name: Upload Coverage to Codecov - name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 uses: codecov/codecov-action@v3
# Run step only pushes to develop and pull_requests # Run step only pushes to develop and pull_requests
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}} if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
with: with:
@@ -149,3 +118,55 @@ jobs:
fail_ci_if_error: false fail_ci_if_error: false
verbose: true verbose: true
token: 6845cc80-77ee-4e17-85a1-026cd95e0766 token: 6845cc80-77ee-4e17-85a1-026cd95e0766
# We upload the artifacts into numbered archives to prevent overwriting
- name: Upload Artifacts
uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
name: snapshots-${{ matrix.containers }}
retention-days: 1
path: ./cypress/snapshots
combineArtifacts:
needs: e2e
runs-on: ubuntu-latest
if: ${{ always() }}
steps:
# Download all snapshot artifacts and merge them into a single folder
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
path: snapshots
pattern: snapshots-*
merge-multiple: true
# For successful push events, we save the snapshots cache
- name: Save snapshots cache
id: cache-upload
if: ${{ github.event_name == 'push' && needs.e2e.result != 'failure' }}
uses: actions/cache/save@v3
with:
path: ./snapshots
key: ${{ runner.os }}-snapshots-${{ github.event.after }}
- name: Flatten images to a folder
if: ${{ needs.e2e.result == 'failure' }}
run: |
mkdir errors
cd snapshots
find . -mindepth 2 -type d -name "*__diff_output__*" -exec sh -c 'mv "$0"/*.png ../errors/' {} \;
- name: Upload Error snapshots
if: ${{ needs.e2e.result == 'failure' }}
uses: actions/upload-artifact@v4
id: upload-artifacts
with:
name: error-snapshots
retention-days: 10
path: errors/
- name: Notify Users
if: ${{ needs.e2e.result == 'failure' }}
run: |
echo "::error title=Visual tests failed::You can view images that failed by downloading the error-snapshots artifact: ${{ steps.upload-artifacts.outputs.artifact-url }}"

View File

@@ -4,17 +4,11 @@ on:
issues: issues:
types: [opened] types: [opened]
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs: jobs:
triage: triage:
permissions:
issues: write # for andymckay/labeler to label issues
pull-requests: write # for andymckay/labeler to label PRs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 - uses: andymckay/labeler@1.0.4
with: with:
repo-token: '${{ secrets.GITHUB_TOKEN }}' repo-token: '${{ secrets.GITHUB_TOKEN }}'
add-labels: 'Status: Triage' add-labels: 'Status: Triage'

View File

@@ -19,9 +19,6 @@ on:
# * is a special character in YAML so you have to quote this string # * is a special character in YAML so you have to quote this string
- cron: '30 8 * * *' - cron: '30 8 * * *'
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs: jobs:
link-checker: link-checker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -29,17 +26,17 @@ jobs:
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting # lychee only uses the GITHUB_TOKEN to avoid rate-limiting
contents: read contents: read
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- name: Restore lychee cache - name: Restore lychee cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 uses: actions/cache@v3
with: with:
path: .lycheecache path: .lycheecache
key: cache-lychee-${{ github.sha }} key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee- restore-keys: cache-lychee-
- name: Link Checker - name: Link Checker
uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a # v1.9.3 uses: lycheeverse/lychee-action@v1.9.3
with: with:
args: >- args: >-
--config .github/lychee.toml --config .github/lychee.toml

View File

@@ -4,32 +4,26 @@ on:
push: push:
merge_group: merge_group:
pull_request: pull_request:
types:
- opened
- synchronize
- ready_for_review
workflow_dispatch: workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: permissions:
contents: write contents: write
jobs: jobs:
docker-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
with:
verbose: true
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json # uses version from "packageManager" field in package.json
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
cache: pnpm cache: pnpm
node-version-file: '.node-version' node-version-file: '.node-version'
@@ -89,9 +83,14 @@ jobs:
continue-on-error: ${{ github.event_name == 'push' }} continue-on-error: ${{ github.event_name == 'push' }}
run: pnpm run docs:verify run: pnpm run docs:verify
- uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable - name: Rebuild Docs
if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }}
working-directory: ./packages/mermaid
run: pnpm run docs:build
- name: Commit changes
uses: EndBug/add-and-commit@v9
if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }}
with: with:
framework: cypress message: 'Update docs'
tests: './cypress/e2e/**/**.spec.js' add: 'docs/*'
token: ${{ secrets.GITHUB_TOKEN }}
has-tests-label: true

View File

@@ -22,7 +22,7 @@ jobs:
pull-requests: write # write permission is required to label PRs pull-requests: write # write permission is required to label PRs
steps: steps:
- name: Label PR - name: Label PR
uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v6.0.0 uses: release-drafter/release-drafter@v5
with: with:
config-name: pr-labeler.yml config-name: pr-labeler.yml
disable-autolabeler: false disable-autolabeler: false

View File

@@ -23,12 +23,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
cache: pnpm cache: pnpm
node-version-file: '.node-version' node-version-file: '.node-version'
@@ -37,13 +37,13 @@ jobs:
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0 uses: actions/configure-pages@v3
- name: Run Build - name: Run Build
run: pnpm --filter mermaid run docs:build:vitepress run: pnpm --filter mermaid run docs:build:vitepress
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 uses: actions/upload-pages-artifact@v1
with: with:
path: packages/mermaid/src/vitepress/.vitepress/dist path: packages/mermaid/src/vitepress/.vitepress/dist
@@ -56,4 +56,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 uses: actions/deploy-pages@v2

23
.github/workflows/release-draft.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Draft Release
on:
push:
branches:
- master
permissions:
contents: read
jobs:
draft-release:
runs-on: ubuntu-latest
permissions:
contents: write # write permission is required to create a GitHub release
pull-requests: read # required to read PR titles/labels
steps:
- name: Draft Release
uses: release-drafter/release-drafter@v5
with:
disable-autolabeler: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -9,14 +9,14 @@ jobs:
publish-preview: publish-preview:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
cache: pnpm cache: pnpm
node-version-file: '.node-version' node-version-file: '.node-version'
@@ -28,7 +28,7 @@ jobs:
CYPRESS_CACHE_FOLDER: .cache/Cypress CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Install Json - name: Install Json
run: npm i json@11.0.0 --global run: npm i json --global
- name: Publish - name: Publish
working-directory: ./packages/mermaid working-directory: ./packages/mermaid

View File

@@ -1,43 +0,0 @@
name: Preview release
on:
pull_request:
branches: [develop]
types: [opened, synchronize, labeled, ready_for_review]
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true
permissions:
contents: read
actions: write
jobs:
preview:
if: ${{ github.repository_owner == 'mermaid-js' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
issues: write
pull-requests: write
name: Publish preview release
timeout-minutes: 5
steps:
- name: Checkout Repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: pnpm install --frozen-lockfile
- name: Publish packages
run: pnpx pkg-pr-new publish --pnpm './packages/*'

47
.github/workflows/release-publish.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Publish release
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: fregante/setup-git-user@v2
- uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: |
pnpm install --frozen-lockfile
npm i json --global
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Prepare release
run: |
VERSION=${GITHUB_REF:10}
echo "Preparing release $VERSION"
git checkout -t origin/release/$VERSION
npm version --no-git-tag-version --allow-same-version $VERSION
git add package.json
git commit -nm "Bump version $VERSION"
git checkout -t origin/master
git merge -m "Release $VERSION" --no-ff release/$VERSION
git push --no-verify
- name: Publish
run: |
npm set //registry.npmjs.org/:_authToken $NPM_TOKEN
npm publish
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,46 +0,0 @@
name: Release
on:
push:
branches:
- master
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
release:
if: github.repository == 'mermaid-js/mermaid'
permissions:
contents: write # to create release (changesets/action)
id-token: write # OpenID Connect token needed for provenance
pull-requests: write # to create pull request (changesets/action)
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'
- name: Install Packages
run: pnpm install --frozen-lockfile
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8
with:
version: pnpm changeset:version
publish: pnpm changeset:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

View File

@@ -1,37 +0,0 @@
name: Scorecard supply-chain security
on:
branch_protection_rule:
push:
branches:
- develop
schedule:
- cron: 29 15 * * 0
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
permissions:
id-token: write
security-events: write
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
with:
sarif_file: results.sarif

View File

@@ -9,13 +9,13 @@ jobs:
unit-test: unit-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
# uses version from "packageManager" field in package.json # uses version from "packageManager" field in package.json
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 uses: actions/setup-node@v4
with: with:
cache: pnpm cache: pnpm
node-version-file: '.node-version' node-version-file: '.node-version'
@@ -38,12 +38,8 @@ jobs:
run: | run: |
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
- name: Verify out-of-tree build with TypeScript
run: |
pnpm test:check:tsc
- name: Upload Coverage to Codecov - name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 uses: codecov/codecov-action@v3
# Run step only pushes to develop and pull_requests # Run step only pushes to develop and pull_requests
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }} if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
with: with:

View File

@@ -8,6 +8,6 @@ jobs:
triage: triage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: Dunning-Kruger/unlock-issues@b06b7f7e5c3f2eaa1c6d5d89f40930e4d6d9699e # v1 - uses: Dunning-Kruger/unlock-issues@v1
with: with:
repo-token: '${{ secrets.GITHUB_TOKEN }}' repo-token: '${{ secrets.GITHUB_TOKEN }}'

View File

@@ -8,18 +8,18 @@ jobs:
update-browser-list: update-browser-list:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@v4
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - uses: pnpm/action-setup@v4
- run: npx update-browserslist-db@latest - run: npx update-browserslist-db@latest
- name: Commit changes - name: Commit changes
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 uses: EndBug/add-and-commit@v9
with: with:
author_name: ${{ github.actor }} author_name: ${{ github.actor }}
author_email: ${{ github.actor }}@users.noreply.github.com author_email: ${{ github.actor }}@users.noreply.github.com
message: 'chore: update browsers list' message: 'chore: update browsers list'
push: false push: false
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0 uses: peter-evans/create-pull-request@v5
with: with:
branch: update-browserslist branch: update-browserslist
title: Update Browserslist title: Update Browserslist

7
.gitignore vendored
View File

@@ -35,7 +35,7 @@ cypress/snapshots/
.tsbuildinfo .tsbuildinfo
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
#knsv*.html knsv*.html
local*.html local*.html
stats/ stats/
@@ -46,9 +46,4 @@ stats/
demos/dev/** demos/dev/**
!/demos/dev/example.html !/demos/dev/example.html
!/demos/dev/reload.js
tsx-0/** tsx-0/**
vite.config.ts.timestamp-*
# autogenereated by langium-cli
generated/

View File

@@ -1,2 +0,0 @@
ignored:
- DL3002 # TODO: Last USER should not be root

4
.husky/commit-msg Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
# . "$(dirname "$0")/_/husky.sh"
# npx --no-install commitlint --edit $1

View File

@@ -1,2 +1,4 @@
#!/usr/bin/env sh #!/bin/sh
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit . "$(dirname "$0")/_/husky.sh"
pnpm run pre-commit

View File

@@ -1 +1 @@
20.12.2 v20.11.1

View File

@@ -11,10 +11,6 @@ stats
.nyc_output .nyc_output
# Autogenerated by `pnpm run --filter mermaid types:build-config` # Autogenerated by `pnpm run --filter mermaid types:build-config`
packages/mermaid/src/config.type.ts packages/mermaid/src/config.type.ts
# autogenereated by langium-cli
generated/
# Ignore the files creates in /demos/dev except for example.html # Ignore the files creates in /demos/dev except for example.html
demos/dev/** demos/dev/**
!/demos/dev/example.html !/demos/dev/example.html
# TODO: Lots of errors to fix
cypress/platform/state-refactor.html

View File

@@ -3,6 +3,5 @@
"printWidth": 100, "printWidth": 100,
"singleQuote": true, "singleQuote": true,
"useTabs": false, "useTabs": false,
"tabWidth": 2, "tabWidth": 2
"trailingComma": "es5"
} }

View File

@@ -1,15 +1,13 @@
import type { InlineConfig } from 'vite'; import { build, InlineConfig, type PluginOption } from 'vite';
import { build, type PluginOption } from 'vite';
import { resolve } from 'path'; import { resolve } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import jisonPlugin from './jisonPlugin.js'; import jisonPlugin from './jisonPlugin.js';
import jsonSchemaPlugin from './jsonSchemaPlugin.js'; import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { readFileSync } from 'fs';
import typescript from '@rollup/plugin-typescript'; import typescript from '@rollup/plugin-typescript';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js'; import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js';
import istanbul from 'vite-plugin-istanbul'; import istanbul from 'vite-plugin-istanbul';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
const visualize = process.argv.includes('--visualize'); const visualize = process.argv.includes('--visualize');
const watch = process.argv.includes('--watch'); const watch = process.argv.includes('--watch');
@@ -38,6 +36,24 @@ const visualizerOptions = (packageName: string, core = false): PluginOption[] =>
); );
}; };
const packageOptions = {
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
};
interface BuildOptions { interface BuildOptions {
minify: boolean | 'esbuild'; minify: boolean | 'esbuild';
core?: boolean; core?: boolean;
@@ -47,18 +63,43 @@ interface BuildOptions {
export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions): InlineConfig => { export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions): InlineConfig => {
const external: (string | RegExp)[] = ['require', 'fs', 'path']; const external: (string | RegExp)[] = ['require', 'fs', 'path'];
// eslint-disable-next-line no-console
console.log(entryName, packageOptions[entryName]); console.log(entryName, packageOptions[entryName]);
const { name, file, packageName } = packageOptions[entryName]; const { name, file, packageName } = packageOptions[entryName];
const output: OutputOptions = [ let output: OutputOptions = [
{ {
name, name,
format: 'esm', format: 'esm',
sourcemap, sourcemap,
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`, entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
}, },
{
name,
format: 'umd',
sourcemap,
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
},
]; ];
if (core) {
const { dependencies } = JSON.parse(
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
);
// Core build is used to generate file without bundled dependencies.
// This is used by downstream projects to bundle dependencies themselves.
// Ignore dependencies and any dependencies of dependencies
// Adapted from the RegEx used by `rollup-plugin-node`
external.push(new RegExp('^(?:' + Object.keys(dependencies).join('|') + ')(?:/.+)?$'));
// This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd.
output = [
{
name,
format: 'esm',
sourcemap,
entryFileNames: `${name}.core.mjs`,
},
];
}
const config: InlineConfig = { const config: InlineConfig = {
configFile: false, configFile: false,
build: { build: {
@@ -85,9 +126,10 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
plugins: [ plugins: [
jisonPlugin(), jisonPlugin(),
jsonSchemaPlugin(), // handles `.schema.yaml` files jsonSchemaPlugin(), // handles `.schema.yaml` files
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
typescript({ compilerOptions: { declaration: false } }), typescript({ compilerOptions: { declaration: false } }),
istanbul({ istanbul({
exclude: ['node_modules', 'test/', '__mocks__', 'generated'], exclude: ['node_modules', 'test/', '__mocks__'],
extension: ['.js', '.ts'], extension: ['.js', '.ts'],
requireEnv: true, requireEnv: true,
forceBuildInstrument: coverage, forceBuildInstrument: coverage,
@@ -107,28 +149,24 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
const buildPackage = async (entryName: keyof typeof packageOptions) => { const buildPackage = async (entryName: keyof typeof packageOptions) => {
await build(getBuildConfig({ minify: false, entryName })); await build(getBuildConfig({ minify: false, entryName }));
await build(getBuildConfig({ minify: 'esbuild', entryName }));
await build(getBuildConfig({ minify: false, core: true, entryName }));
}; };
const main = async () => { const main = async () => {
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[]; const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
for (const pkg of packageNames.filter( for (const pkg of packageNames.filter((pkg) => !mermaidOnly || pkg === 'mermaid')) {
(pkg) => !mermaidOnly || pkg === 'mermaid' || pkg === 'parser'
)) {
await buildPackage(pkg); await buildPackage(pkg);
} }
}; };
await generateLangium();
if (watch) { if (watch) {
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' })); build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
void build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
if (!mermaidOnly) { if (!mermaidOnly) {
void build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
void build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' })); build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
} }
} else if (visualize) { } else if (visualize) {
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' })); await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' })); await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
} else { } else {

View File

@@ -1,10 +1,10 @@
import { transformJison } from '../.build/jisonTransformer.js'; import { transformJison } from './jisonTransformer.js';
const fileRegex = /\.(jison)$/; const fileRegex = /\.(jison)$/;
export default function jison() { export default function jison() {
return { return {
name: 'jison', name: 'jison',
transform(src: string, id: string) { transform(src: string, id: string) {
if (fileRegex.test(id)) { if (fileRegex.test(id)) {
return { return {

View File

@@ -1,7 +1,6 @@
import jison from 'jison'; import jison from 'jison';
export const transformJison = (src: string): string => { export const transformJison = (src: string): string => {
// @ts-ignore - Jison is not typed properly
const parser = new jison.Generator(src, { const parser = new jison.Generator(src, {
moduleType: 'js', moduleType: 'js',
'token-stack': true, 'token-stack': true,

View File

@@ -1,5 +1,110 @@
import type { PluginOption } from 'vite'; import { load, JSON_SCHEMA } from 'js-yaml';
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js'; 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',
'xyChart',
'requirement',
'mindmap',
'timeline',
'gitGraph',
'c4',
'sankey',
'block',
] 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. * Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
@@ -16,13 +121,32 @@ export default function jsonSchemaPlugin(): PluginOption {
return; return;
} }
const jsonSchema = loadSchema(src, idAsUrl.pathname); if (idAsUrl.searchParams.get('only-defaults')) {
return { const jsonSchema = load(src, {
code: idAsUrl.searchParams.get('only-defaults') filename: idAsUrl.pathname,
? getDefaults(jsonSchema) // only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
: getSchema(jsonSchema), // e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
map: null, // no source map 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
};
}
}, },
}; };
} }

View File

@@ -1,7 +1,6 @@
import express from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
import { createServer as createViteServer } from 'vite'; import { createServer as createViteServer } from 'vite';
import { packageOptions } from '../.build/common.js';
async function createServer() { async function createServer() {
const app = express(); const app = express();
@@ -15,17 +14,16 @@ async function createServer() {
}); });
app.use(cors()); app.use(cors());
for (const { packageName } of Object.values(packageOptions)) { app.use(express.static('./packages/mermaid/dist'));
app.use(express.static(`./packages/${packageName}/dist`)); app.use(express.static('./packages/mermaid-zenuml/dist'));
} app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(vite.middlewares); app.use(vite.middlewares);
app.use(express.static('demos')); app.use(express.static('demos'));
app.use(express.static('cypress/platform')); app.use(express.static('cypress/platform'));
app.listen(9000, () => { app.listen(9000, () => {
// eslint-disable-next-line no-console
console.log(`Listening on http://localhost:9000`); console.log(`Listening on http://localhost:9000`);
}); });
} }
void createServer(); createServer();

View File

@@ -1,13 +1,2 @@
FROM node:20.12.2-alpine3.19@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2 FROM node:20.11.1-alpine3.19 AS base
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
USER 0:0
RUN corepack enable \
&& corepack enable pnpm
RUN apk add --no-cache git~=2.43.4 \
&& git config --add --system safe.directory /mermaid
ENV NODE_OPTIONS="--max_old_space_size=8192"
EXPOSE 9000 3333

View File

@@ -1,7 +0,0 @@
{
"drips": {
"ethereum": {
"ownedBy": "0x0831DDFe60d009d9448CC976157b539089aB821E"
}
}
}

View File

@@ -35,8 +35,6 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid)
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3) [![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_) [![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mermaid-js/mermaid/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mermaid-js/mermaid)
<img src="./img/header.png" alt="" /> <img src="./img/header.png" alt="" />
@@ -83,10 +81,6 @@ You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](https://mermaid.js.org/intro/getting-started.html), [Usage](https://mermaid.js.org/config/usage.html) and [Tutorials](https://mermaid.js.org/ecosystem/tutorials.html). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](https://mermaid.js.org/intro/getting-started.html), [Usage](https://mermaid.js.org/config/usage.html) and [Tutorials](https://mermaid.js.org/ecosystem/tutorials.html).
Our PR Visual Regression Testing is powered by [Argos](https://argos-ci.com/?utm_source=mermaid&utm_campaign=oss) with their generous Open Source plan. It makes the process of reviewing PRs with visual changes a breeze.
[![Covered by Argos Visual Testing](https://argos-ci.com/badge-large.svg)](https://argos-ci.com?utm_source=mermaid&utm_campaign=oss)
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
<a href="https://applitools.com/"> <a href="https://applitools.com/">

21
__mocks__/c4Renderer.js Normal file
View File

@@ -0,0 +1,21 @@
/**
* Mocked C4Context diagram renderer
*/
import { vi } from 'vitest';
export const drawPersonOrSystemArray = vi.fn();
export const drawBoundary = vi.fn();
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
drawPersonOrSystemArray,
drawBoundary,
setConf,
draw,
};

View File

@@ -0,0 +1,16 @@
/**
* Mocked class diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -0,0 +1,13 @@
/**
* Mocked class diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

1
__mocks__/dagre-d3.ts Normal file
View File

@@ -0,0 +1 @@
// DO NOT delete this file. It is used by vitest to mock the dagre-d3 module.

View File

@@ -0,0 +1,3 @@
module.exports = function (txt: string) {
return txt;
};

16
__mocks__/erRenderer.js Normal file
View File

@@ -0,0 +1,16 @@
/**
* Mocked er diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -0,0 +1,24 @@
/**
* Mocked flow (flowchart) diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const addVertices = vi.fn();
export const addEdges = vi.fn();
export const getClasses = vi.fn().mockImplementation(() => {
return {};
});
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
addVertices,
addEdges,
getClasses,
draw,
};

View File

@@ -0,0 +1,16 @@
/**
* Mocked gantt diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -0,0 +1,13 @@
/**
* Mocked git (graph) diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -0,0 +1,15 @@
/**
* Mocked pie (picChart) diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

8
__mocks__/pieRenderer.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* Mocked pie (picChart) diagram renderer
*/
import { vi } from 'vitest';
const draw = vi.fn().mockImplementation(() => '');
export const renderer = { draw };

View File

@@ -0,0 +1,13 @@
/**
* Mocked requirement diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -0,0 +1,13 @@
/**
* Mocked Sankey diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -0,0 +1,23 @@
/**
* Mocked sequence diagram renderer
*/
import { vi } from 'vitest';
export const bounds = vi.fn();
export const drawActors = vi.fn();
export const drawActorsPopup = vi.fn();
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
bounds,
drawActors,
drawActorsPopup,
setConf,
draw,
};

View File

@@ -0,0 +1,22 @@
/**
* Mocked state diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const getClasses = vi.fn().mockImplementation(() => {
return {};
});
export const stateDomId = vi.fn().mockImplementation(() => {
return 'mocked-stateDiagram-stateDomId';
});
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
getClasses,
draw,
};

View File

@@ -1,10 +1,7 @@
import eyesPlugin from '@applitools/eyes-cypress';
import { registerArgosTask } from '@argos-ci/cypress/task';
import coverage from '@cypress/code-coverage/task';
import { defineConfig } from 'cypress'; import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import cypressSplit from 'cypress-split'; import coverage from '@cypress/code-coverage/task';
import eyesPlugin from '@applitools/eyes-cypress';
export default eyesPlugin( export default eyesPlugin(
defineConfig({ defineConfig({
projectId: 'n2sma2', projectId: 'n2sma2',
@@ -14,24 +11,16 @@ export default eyesPlugin(
specPattern: 'cypress/integration/**/*.{js,ts}', specPattern: 'cypress/integration/**/*.{js,ts}',
setupNodeEvents(on, config) { setupNodeEvents(on, config) {
coverage(on, config); coverage(on, config);
cypressSplit(on, config);
on('before:browser:launch', (browser, launchOptions) => { on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) { if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1'); launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1');
} }
return launchOptions; return launchOptions;
}); });
addMatchImageSnapshotPlugin(on, config);
// copy any needed variables from process.env to config.env // copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false; config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.CI;
if (config.env.useArgos) {
registerArgosTask(on, config, {
token: 'fc3a35cf5200db928d65b2047861582d9444032b',
});
} else {
addMatchImageSnapshotPlugin(on, config);
}
// do not forget to return the changed config object! // do not forget to return the changed config object!
return config; return config;
}, },

View File

@@ -29,14 +29,13 @@ export const mermaidUrl = (
options: CypressMermaidConfig, options: CypressMermaidConfig,
api: boolean api: boolean
): string => { ): string => {
options.handDrawnSeed = 1;
const codeObject: CodeObject = { const codeObject: CodeObject = {
code: graphStr, code: graphStr,
mermaid: options, mermaid: options,
}; };
const objStr: string = JSON.stringify(codeObject); const objStr: string = JSON.stringify(codeObject);
let url = `http://localhost:9000/e2e.html?graph=${utf8ToB64(objStr)}`; let url = `http://localhost:9000/e2e.html?graph=${utf8ToB64(objStr)}`;
if (api && typeof graphStr === 'string') { if (api) {
url = `http://localhost:9000/xss.html?graph=${graphStr}`; url = `http://localhost:9000/xss.html?graph=${graphStr}`;
} }
@@ -55,15 +54,16 @@ export const imgSnapshotTest = (
): void => { ): void => {
const options: CypressMermaidConfig = { const options: CypressMermaidConfig = {
..._options, ..._options,
fontFamily: _options.fontFamily ?? 'courier', fontFamily: _options.fontFamily || 'courier',
// @ts-ignore TODO: Fix type of fontSize // @ts-ignore TODO: Fix type of fontSize
fontSize: _options.fontSize ?? '16px', fontSize: _options.fontSize || '16px',
sequence: { sequence: {
...(_options.sequence ?? {}), ...(_options.sequence || {}),
actorFontFamily: 'courier', actorFontFamily: 'courier',
noteFontFamily: _options.sequence?.noteFontFamily noteFontFamily:
? _options.sequence.noteFontFamily _options.sequence && _options.sequence.noteFontFamily
: 'courier', ? _options.sequence.noteFontFamily
: 'courier',
messageFontFamily: 'courier', messageFontFamily: 'courier',
}, },
}; };
@@ -74,7 +74,7 @@ export const imgSnapshotTest = (
export const urlSnapshotTest = ( export const urlSnapshotTest = (
url: string, url: string,
options: CypressMermaidConfig = {}, options: CypressMermaidConfig,
_api = false, _api = false,
validation?: any validation?: any
): void => { ): void => {
@@ -95,22 +95,8 @@ export const openURLAndVerifyRendering = (
options: CypressMermaidConfig, options: CypressMermaidConfig,
validation?: any validation?: any
): void => { ): void => {
const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
if (validation) {
cy.get('svg').should(validation);
}
verifyScreenshot(name);
};
export const verifyScreenshot = (name: string): void => {
const useAppli: boolean = Cypress.env('useAppli'); const useAppli: boolean = Cypress.env('useAppli');
const useArgos: boolean = Cypress.env('useArgos'); const name: string = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) { if (useAppli) {
cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`); cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
@@ -120,14 +106,21 @@ export const verifyScreenshot = (name: string): void => {
batchName: Cypress.spec.name, batchName: Cypress.spec.name,
batchId: batchId + 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.log(`Check eyes ${Cypress.spec.name}`);
cy.eyesCheckWindow('Click!'); cy.eyesCheckWindow('Click!');
cy.log(`Closing eyes ${Cypress.spec.name}`); cy.log(`Closing eyes ${Cypress.spec.name}`);
cy.eyesClose(); cy.eyesClose();
} else if (useArgos) {
cy.argosScreenshot(name, {
threshold: 0.01,
});
} else { } else {
cy.matchImageSnapshot(name); cy.matchImageSnapshot(name);
} }

View File

@@ -1,4 +1,4 @@
import { renderGraph, verifyScreenshot } from '../../helpers/util.ts'; import { renderGraph } from '../../helpers/util.ts';
describe('Configuration', () => { describe('Configuration', () => {
describe('arrowMarkerAbsolute', () => { describe('arrowMarkerAbsolute', () => {
it('should handle default value false of arrowMarkerAbsolute', () => { it('should handle default value false of arrowMarkerAbsolute', () => {
@@ -118,52 +118,11 @@ describe('Configuration', () => {
it('should not taint the initial configuration when using multiple directives', () => { it('should not taint the initial configuration when using multiple directives', () => {
const url = 'http://localhost:9000/regression/issue-1874.html'; const url = 'http://localhost:9000/regression/issue-1874.html';
cy.visit(url); cy.visit(url);
cy.window().should('have.property', 'rendered', true);
verifyScreenshot( cy.get('svg');
cy.matchImageSnapshot(
'configuration.spec-should-not-taint-initial-configuration-when-using-multiple-directives' 'configuration.spec-should-not-taint-initial-configuration-when-using-multiple-directives'
); );
}); });
}); });
describe('suppressErrorRendering', () => {
beforeEach(() => {
cy.on('uncaught:exception', (err, runnable) => {
return !err.message.includes('Parse error on line');
});
});
it('should not render error diagram if suppressErrorRendering is set', () => {
const url = 'http://localhost:9000/suppressError.html?suppressErrorRendering=true';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('#test')
.find('svg')
.should(($svg) => {
// all failing diagrams should not appear!
expect($svg).to.have.length(2);
// none of the diagrams should be error diagrams
expect($svg).to.not.contain('Syntax error');
});
verifyScreenshot(
'configuration.spec-should-not-render-error-diagram-if-suppressErrorRendering-is-set'
);
});
it('should render error diagram if suppressErrorRendering is not set', () => {
const url = 'http://localhost:9000/suppressError.html';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('#test')
.find('svg')
.should(($svg) => {
// all five diagrams should be rendered
expect($svg).to.have.length(5);
// some of the diagrams should be error diagrams
expect($svg).to.contain('Syntax error');
});
verifyScreenshot(
'configuration.spec-should-render-error-diagram-if-suppressErrorRendering-is-not-set'
);
});
});
}); });

View File

@@ -1,11 +0,0 @@
describe('IIFE', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/iife.html');
});
it('should render when using mermaid.min.js', () => {
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
cy.get('#d2').should('contain', 'Hello');
});
});

View File

@@ -0,0 +1,16 @@
describe('Sequencediagram', () => {
it('should render a simple sequence diagrams', () => {
const url = 'http://localhost:9000/webpackUsage.html';
cy.visit(url);
cy.get('body').find('svg').should('have.length', 1);
});
it('should handle html escapings properly', () => {
const url = 'http://localhost:9000/webpackUsage.html?test-html-escaping=true';
cy.visit(url);
cy.get('body').find('svg').should('have.length', 1);
cy.get('g.label > foreignobject > div').should('not.contain.text', '<b>');
});
});

View File

@@ -10,6 +10,7 @@ describe('XSS', () => {
cy.wait(1000).then(() => { cy.wait(1000).then(() => {
cy.get('.mermaid').should('exist'); cy.get('.mermaid').should('exist');
}); });
cy.get('svg');
}); });
it('should not allow tags in the css', () => { it('should not allow tags in the css', () => {

View File

@@ -11,27 +11,6 @@ describe('Git Graph diagram', () => {
{} {}
); );
}); });
it('Should render subgraphs with title margins and edge labels', () => {
imgSnapshotTest(
`flowchart LR
subgraph TOP
direction TB
subgraph B1
direction RL
i1 --lb1-->f1
end
subgraph B2
direction BT
i2 --lb2-->f2
end
end
A --lb3--> TOP --lb4--> B
B1 --lb5--> B2
`,
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
);
});
// it(`ultraFastTest`, function () { // it(`ultraFastTest`, function () {
// // Navigate to the url we want to test // // Navigate to the url we want to test
// // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run. // // ⭐️ Note to see visual bugs, run the test using the above URL for the 1st run.

View File

@@ -1,181 +0,0 @@
import { imgSnapshotTest, urlSnapshotTest } from '../../helpers/util.ts';
describe.skip('architecture diagram', () => {
it('should render a simple architecture diagram with groups', () => {
imgSnapshotTest(
`architecture-beta
group api(cloud)[API]
service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
service gateway(internet)[Gateway]
db L--R server
disk1 T--B server
disk2 T--B db
server T--B gateway
`
);
});
it('should render an architecture diagram with groups within groups', () => {
imgSnapshotTest(
`architecture-beta
group api[API]
group public[Public API] in api
group private[Private API] in api
service serv1(server)[Server] in public
service serv2(server)[Server] in private
service db(database)[Database] in private
service gateway(internet)[Gateway] in api
serv1 B--T serv2
serv2 L--R db
serv1 L--R gateway
`
);
});
it('should render an architecture diagram with the fallback icon', () => {
imgSnapshotTest(
`architecture-beta
service unknown(iconnamedoesntexist)[Unknown Icon]
`
);
});
it('should render an architecture diagram with split directioning', () => {
imgSnapshotTest(
`architecture-beta
service db(database)[Database]
service s3(disk)[Storage]
service serv1(server)[Server 1]
service serv2(server)[Server 2]
service disk(disk)[Disk]
db L--R s3
serv1 L--T s3
serv2 L--B s3
serv1 T--B disk
`
);
});
it('should render an architecture diagram with directional arrows', () => {
imgSnapshotTest(
`architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC (L--R) servL
servC (R--L) servR
servC (T--B) servT
servC (B--T) servB
servL (T--L) servT
servL (B--L) servB
servR (T--R) servT
servR (B--R) servB
`
);
});
it('should render an architecture diagram with group edges', () => {
imgSnapshotTest(
`architecture-beta
group left_group(cloud)[Left]
group right_group(cloud)[Right]
group top_group(cloud)[Top]
group bottom_group(cloud)[Bottom]
group center_group(cloud)[Center]
service left_disk(disk)[Disk] in left_group
service right_disk(disk)[Disk] in right_group
service top_disk(disk)[Disk] in top_group
service bottom_disk(disk)[Disk] in bottom_group
service center_disk(disk)[Disk] in center_group
left_disk{group} (R--L) center_disk{group}
right_disk{group} (L--R) center_disk{group}
top_disk{group} (B--T) center_disk{group}
bottom_disk{group} (T--B) center_disk{group}
`
);
});
it('should render an architecture diagram with edge labels', () => {
imgSnapshotTest(
`architecture-beta
service servC(server)[Server 1]
service servL(server)[Server 2]
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]
servC L-[Label]-R servL
servC R-[Label]-L servR
servC T-[Label]-B servT
servC B-[Label]-T servB
servL T-[Label]-L servT
servL B-[Label]-L servB
servR T-[Label]-R servT
servR B-[Label]-R servB
`
);
});
it('should render an architecture diagram with simple junction edges', () => {
imgSnapshotTest(
`architecture-beta
service left_disk(disk)[Disk]
service top_disk(disk)[Disk]
service bottom_disk(disk)[Disk]
service top_gateway(internet)[Gateway]
service bottom_gateway(internet)[Gateway]
junction juncC
junction juncR
left_disk R--L juncC
top_disk B--T juncC
bottom_disk T--B juncC
juncC R--L juncR
top_gateway B--T juncR
bottom_gateway T--B juncR
`
);
});
it('should render an architecture diagram with complex junction edges', () => {
imgSnapshotTest(
`architecture-beta
group left
group right
service left_disk(disk)[Disk] in left
service top_disk(disk)[Disk] in left
service bottom_disk(disk)[Disk] in left
service top_gateway(internet)[Gateway] in right
service bottom_gateway(internet)[Gateway] in right
junction juncC in left
junction juncR in right
left_disk R--L juncC
top_disk B--T juncC
bottom_disk T--B juncC
top_gateway (B--T juncR
bottom_gateway (T--B juncR
juncC{group} R--L) juncR{group}
`
);
});
});
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
describe.skip('architecture - external', () => {
it('should allow adding external icons', () => {
urlSnapshotTest('http://localhost:9000/architecture-external.html');
});
});

View File

@@ -236,7 +236,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL17: width alignment - blocks shold be equal in width', () => { it('BL16: width alignment - blocks shold be equal in width', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A("This is the text") A("This is the text")
@@ -247,7 +247,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL18: block types 1 - square, rounded and circle', () => { it('BL17: block types 1 - square, rounded and circle', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A["square"] A["square"]
@@ -258,7 +258,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL19: block types 2 - odd, diamond and hexagon', () => { it('BL18: block types 2 - odd, diamond and hexagon', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A>"rect_left_inv_arrow"] A>"rect_left_inv_arrow"]
@@ -269,7 +269,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL20: block types 3 - stadium', () => { it('BL19: block types 3 - stadium', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A(["stadium"]) A(["stadium"])
@@ -278,7 +278,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL21: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => { it('BL20: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A[/"lean right"/] A[/"lean right"/]
@@ -290,7 +290,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL22: block types 1 - square, rounded and circle', () => { it('BL21: block types 1 - square, rounded and circle', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A["square"] A["square"]
@@ -301,7 +301,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL23: sizing - it should be possible to make a block wider', () => { it('BL22: sizing - it should be possible to make a block wider', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
A("rounded"):2 A("rounded"):2
@@ -312,7 +312,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL24: sizing - it should be possible to make a composite block wider', () => { it('BL23: sizing - it should be possible to make a composite block wider', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
block:2 block:2
@@ -324,7 +324,7 @@ describe('Block diagram', () => {
); );
}); });
it('BL25: block in the middle with space on each side', () => { it('BL24: block in the middle with space on each side', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@@ -335,7 +335,7 @@ describe('Block diagram', () => {
{} {}
); );
}); });
it('BL26: space and an edge', () => { it('BL25: space and an edge', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 5 columns 5
@@ -345,7 +345,7 @@ describe('Block diagram', () => {
{} {}
); );
}); });
it('BL27: block sizes for regular blocks', () => { it('BL26: block sizes for regular blocks', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@@ -354,7 +354,7 @@ describe('Block diagram', () => {
{} {}
); );
}); });
it('BL28: composite block with a set width - f should use the available space', () => { it('BL27: composite block with a set width - f should use the available space', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@@ -363,12 +363,11 @@ describe('Block diagram', () => {
f f
end end
g g
`, `,
{} {}
); );
}); });
it('BL23: composite block with a set width - f and g should split the available space', () => {
it('BL29: composite block with a set width - f and g should split the available space', () => {
imgSnapshotTest( imgSnapshotTest(
`block-beta `block-beta
columns 3 columns 3
@@ -380,7 +379,7 @@ describe('Block diagram', () => {
h h
i i
j j
`, `,
{} {}
); );
}); });

View File

@@ -1,7 +1,7 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('C4 diagram', () => { describe('C4 diagram', () => {
it('C4.1 should render a simple C4Context diagram', () => { it('should render a simple C4Context diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Context C4Context
@@ -30,8 +30,9 @@ describe('C4 diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('C4.2 should render a simple C4Container diagram', () => { it('should render a simple C4Container diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Container C4Container
@@ -49,8 +50,9 @@ describe('C4 diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('C4.3 should render a simple C4Component diagram', () => { it('should render a simple C4Component diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Component C4Component
@@ -67,8 +69,9 @@ describe('C4 diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('C4.4 should render a simple C4Dynamic diagram', () => { it('should render a simple C4Dynamic diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Dynamic C4Dynamic
@@ -90,8 +93,9 @@ describe('C4 diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('C4.5 should render a simple C4Deployment diagram', () => { it('should render a simple C4Deployment diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `
C4Deployment C4Deployment
@@ -113,5 +117,6 @@ describe('C4 diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
}); });

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -76,7 +76,7 @@ describe('Class diagram V2', () => {
); );
}); });
it('2.1 should render a simple class diagram with different visibilities', () => { it('should render a simple class diagram with different visibilities', () => {
imgSnapshotTest( imgSnapshotTest(
` `
classDiagram-v2 classDiagram-v2
@@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
); );
}); });
it('3: should render multiple class diagrams', () => { it('should render multiple class diagrams', () => {
imgSnapshotTest( imgSnapshotTest(
[ [
` `
@@ -581,63 +581,4 @@ class C13["With Città foreign language"]
{ logLevel: 1, flowchart: { htmlLabels: false } } { logLevel: 1, flowchart: { htmlLabels: false } }
); );
}); });
it('renders a class diagram with a generic class in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class GenericClass~T~ {
+addItem(item: T)
+getItem() T
}
}
`;
imgSnapshotTest(diagramDefinition);
});
it('renders a class diagram with nested namespaces and relationships', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module.SubModule {
class Report {
+generatePDF(data: List)
+generateCSV(data: List)
}
}
namespace Company.Project.Module {
class Admin {
+generateReport()
}
}
Admin --> Report : generates
`;
imgSnapshotTest(diagramDefinition);
});
it('renders a class diagram with multiple classes and relationships in a namespace', () => {
const diagramDefinition = `
classDiagram-v2
namespace Company.Project.Module {
class User {
+login(username: String, password: String)
+logout()
}
class Admin {
+addUser(user: User)
+removeUser(user: User)
+generateReport()
}
class Report {
+generatePDF(reportData: List)
+generateCSV(reportData: List)
}
}
Admin --> User : manages
Admin --> Report : generates
`;
imgSnapshotTest(diagramDefinition);
});
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@ describe('Class diagram', () => {
`, `,
{ logLevel: 1 } { logLevel: 1 }
); );
cy.get('svg');
}); });
it('2: should render a simple class diagrams with cardinality', () => { it('2: should render a simple class diagrams with cardinality', () => {
@@ -60,6 +61,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('3: should render a simple class diagram with different visibilities', () => { it('3: should render a simple class diagram with different visibilities', () => {
@@ -77,6 +79,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('4: should render a simple class diagram with comments', () => { it('4: should render a simple class diagram with comments', () => {
@@ -106,6 +109,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('5: should render a simple class diagram with abstract method', () => { it('5: should render a simple class diagram with abstract method', () => {
@@ -117,6 +121,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('6: should render a simple class diagram with static method', () => { it('6: should render a simple class diagram with static method', () => {
@@ -128,6 +133,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('7: should render a simple class diagram with Generic class', () => { it('7: should render a simple class diagram with Generic class', () => {
@@ -147,6 +153,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('8: should render a simple class diagram with Generic class and relations', () => { it('8: should render a simple class diagram with Generic class and relations', () => {
@@ -167,6 +174,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('9: should render a simple class diagram with clickable link', () => { it('9: should render a simple class diagram with clickable link', () => {
@@ -188,6 +196,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('10: should render a simple class diagram with clickable callback', () => { it('10: should render a simple class diagram with clickable callback', () => {
@@ -209,6 +218,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('11: should render a simple class diagram with return type on method', () => { it('11: should render a simple class diagram with return type on method', () => {
@@ -223,6 +233,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('12: should render a simple class diagram with generic types', () => { it('12: should render a simple class diagram with generic types', () => {
@@ -238,6 +249,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('13: should render a simple class diagram with css classes applied', () => { it('13: should render a simple class diagram with css classes applied', () => {
@@ -255,6 +267,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('14: should render a simple class diagram with css classes applied directly', () => { it('14: should render a simple class diagram with css classes applied directly', () => {
@@ -270,6 +283,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('15: should render a simple class diagram with css classes applied to multiple classes', () => { it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
@@ -284,6 +298,7 @@ describe('Class diagram', () => {
`, `,
{} {}
); );
cy.get('svg');
}); });
it('16: should render multiple class diagrams', () => { it('16: should render multiple class diagrams', () => {
@@ -336,6 +351,7 @@ describe('Class diagram', () => {
], ],
{} {}
); );
cy.get('svg');
}); });
// it('17: should render a class diagram when useMaxWidth is true (default)', () => { // it('17: should render a class diagram when useMaxWidth is true (default)', () => {
@@ -405,6 +421,7 @@ describe('Class diagram', () => {
`, `,
{ logLevel: 1 } { logLevel: 1 }
); );
cy.get('svg');
}); });
it('should render class diagram with newlines in title', () => { it('should render class diagram with newlines in title', () => {
@@ -422,6 +439,7 @@ describe('Class diagram', () => {
+quack() +quack()
} }
`); `);
cy.get('svg');
}); });
it('should render class diagram with many newlines in title', () => { it('should render class diagram with many newlines in title', () => {

View File

@@ -218,6 +218,7 @@ describe('Entity Relationship Diagram', () => {
`, `,
{ loglevel: 1 } { loglevel: 1 }
); );
cy.get('svg');
}); });
it('should render entities with keys', () => { it('should render entities with keys', () => {
@@ -321,37 +322,4 @@ ORDER ||--|{ LINE-ITEM : contains
{ logLevel: 1 } { logLevel: 1 }
); );
}); });
it('should render relationship labels with line breaks', () => {
imgSnapshotTest(
`
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
b["Customer Account Secondary"] {
string email
}
c["Customer Account Tertiary"] {
string email
}
d["Customer Account Nth"] {
string email
}
p ||--o| a : "has<br />one"
p ||--o| b : "has<br />one<br />two"
p ||--o| c : "has<br />one<br/>two<br />three"
p ||--o| d : "has<br />one<br />two<br/>three<br />...<br/>Nth"
`,
{ logLevel: 1 }
);
});
}); });

View File

@@ -3,7 +3,7 @@ import { imgSnapshotTest } from '../../helpers/util';
describe('Error Diagrams', () => { describe('Error Diagrams', () => {
beforeEach(() => { beforeEach(() => {
cy.on('uncaught:exception', (err) => { cy.on('uncaught:exception', (err) => {
expect(err.message).to.include('error'); expect(err.message).to.include('Parse error');
// return false to prevent the error from // return false to prevent the error from
// failing this test // failing this test
return false; return false;

View File

@@ -1,6 +1,6 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts'; import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('Flowchart ELK', () => { describe.skip('Flowchart ELK', () => {
it('1-elk: should render a simple flowchart', () => { it('1-elk: should render a simple flowchart', () => {
imgSnapshotTest( imgSnapshotTest(
`flowchart-elk TD `flowchart-elk TD
@@ -837,69 +837,6 @@ subgraph "\`**Two**\`"
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog") in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end end
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('Sub graphs and markdown strings', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph subgraph_ko6czgs5u["Untitled subgraph"]
D["Option 1"]
end
C{"Evaluate"} -- One --> D
C -- Two --> E(("Option 2"))
D --> E
A["A"]
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6080: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}
`, `,
{ flowchart: { titleTopMargin: 0 } } { flowchart: { titleTopMargin: 0 } }
); );
@@ -907,41 +844,3 @@ flowchart LR
}); });
}); });
}); });
describe('Title and arrow styling #4813', () => {
it('should render a flowchart with title', () => {
const titleString = 'Test Title';
renderGraph(
`---
title: ${titleString}
---
flowchart LR
A-->B
A-->C`,
{ layout: 'elk' }
);
cy.get('svg').should((svg) => {
const title = svg[0].querySelector('text');
expect(title.textContent).to.contain(titleString);
});
});
it('Render with stylized arrows', () => {
renderGraph(
`
flowchart LR
A-->B
B-.-oC
C==xD
D ~~~ A`,
{ layout: 'elk' }
);
cy.get('svg').should((svg) => {
const edges = svg[0].querySelectorAll('.edges path');
expect(edges[0].getAttribute('class')).to.contain('edge-pattern-solid');
expect(edges[1].getAttribute('class')).to.contain('edge-pattern-dotted');
expect(edges[2].getAttribute('class')).to.contain('edge-thickness-thick');
expect(edges[3].getAttribute('class')).to.contain('edge-thickness-invisible');
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,142 +0,0 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
const aliasSet1 = ['process', 'rect', 'proc', 'rectangle'] as const;
const aliasSet2 = ['event', 'rounded'] as const;
const aliasSet3 = ['stadium', 'pill', 'terminal'] as const;
const aliasSet4 = ['fr-rect', 'subproc', 'subprocess', 'framed-rectangle', 'subroutine'] as const;
const aliasSet5 = ['db', 'database', 'cylinder', 'cyl'] as const;
const aliasSet6 = ['diam', 'decision', 'diamond'] as const;
const aliasSet7 = ['hex', 'hexagon', 'prepare'] as const;
const aliasSet8 = ['lean-r', 'lean-right', 'in-out'] as const;
const aliasSet9 = ['lean-l', 'lean-left', 'out-in'] as const;
const aliasSet10 = ['trap-b', 'trapezoid-bottom', 'priority'] as const;
const aliasSet11 = ['trap-t', 'trapezoid-top', 'manual'] as const;
const aliasSet12 = ['dbl-circ', 'double-circle'] as const;
const aliasSet13 = ['notched-rectangle', 'card', 'notch-rect'] as const;
const aliasSet14 = [
'lin-rect',
'lined-rectangle',
'lin-proc',
'lined-process',
'shaded-process',
] as const;
const aliasSet15 = ['sm-circ', 'small-circle', 'start'] as const;
const aliasSet16 = ['fr-circ', 'framed-circle', 'stop'] as const;
const aliasSet17 = ['fork', 'join'] as const;
// brace-r', 'braces'
const aliasSet18 = ['comment', 'brace-l'] as const;
const aliasSet19 = ['bolt', 'com-link', 'lightning-bolt'] as const;
const aliasSet20 = ['doc', 'document'] as const;
const aliasSet21 = ['delay', 'half-rounded-rectangle'] as const;
const aliasSet22 = ['h-cyl', 'das', 'horizontal-cylinder'] as const;
const aliasSet23 = ['lin-cyl', 'disk', 'lined-cylinder'] as const;
const aliasSet24 = ['curv-trap', 'display', 'curved-trapezoid'] as const;
const aliasSet25 = ['div-rect', 'div-proc', 'divided-rectangle', 'divided-process'] as const;
const aliasSet26 = ['extract', 'tri', 'triangle'] as const;
const aliasSet27 = ['win-pane', 'internal-storage', 'window-pane'] as const;
const aliasSet28 = ['f-circ', 'junction', 'filled-circle'] as const;
const aliasSet29 = ['lin-doc', 'lined-document'] as const;
const aliasSet30 = ['notch-pent', 'loop-limit', 'notched-pentagon'] as const;
const aliasSet31 = ['flip-tri', 'manual-file', 'flipped-triangle'] as const;
const aliasSet32 = ['sl-rect', 'manual-input', 'sloped-rectangle'] as const;
const aliasSet33 = ['docs', 'documents', 'st-doc', 'stacked-document'] as const;
const aliasSet34 = ['procs', 'processes', 'st-rect', 'stacked-rectangle'] as const;
const aliasSet35 = ['flag', 'paper-tape'] as const;
const aliasSet36 = ['bow-rect', 'stored-data', 'bow-tie-rectangle'] as const;
const aliasSet37 = ['cross-circ', 'summary', 'crossed-circle'] as const;
const aliasSet38 = ['tag-doc', 'tagged-document'] as const;
const aliasSet39 = ['tag-rect', 'tag-proc', 'tagged-rectangle', 'tagged-process'] as const;
const aliasSet40 = ['collate', 'hourglass'] as const;
// Aggregate all alias sets into a single array
const aliasSets = [
aliasSet1,
aliasSet2,
aliasSet3,
aliasSet4,
aliasSet5,
aliasSet6,
aliasSet7,
aliasSet8,
aliasSet9,
aliasSet10,
aliasSet11,
aliasSet12,
aliasSet13,
aliasSet14,
aliasSet15,
aliasSet16,
aliasSet17,
aliasSet18,
aliasSet19,
aliasSet20,
aliasSet21,
aliasSet22,
aliasSet23,
aliasSet24,
aliasSet25,
aliasSet26,
aliasSet27,
aliasSet28,
aliasSet29,
aliasSet30,
aliasSet31,
aliasSet32,
aliasSet33,
aliasSet34,
aliasSet35,
aliasSet36,
aliasSet37,
aliasSet38,
aliasSet39,
] as const;
aliasSets.forEach((aliasSet) => {
describe(`Test ${aliasSet.join(',')} `, () => {
it(`All ${aliasSet.join(',')} should render same shape`, () => {
let flowchartCode = `flowchart \n`;
aliasSet.forEach((alias, index) => {
flowchartCode += ` n${index}@{ shape: ${alias}, label: "${alias}" }\n`;
});
imgSnapshotTest(flowchartCode);
});
});
});

View File

@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
const style = svg.attr('style'); const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/); expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05); expect(maxWidthValue).to.be.within(290 * 0.95 - 1, 290 * 1.05);
}); });
}); });
it('8: should render a flowchart when useMaxWidth is false', () => { it('8: should render a flowchart when useMaxWidth is false', () => {
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
const width = parseFloat(svg.attr('width')); const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±5% // use within because the absolute value can be slightly different depending on the environment ±5%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05); // expect(height).to.be.within(446 * 0.95, 446 * 1.05);
expect(width).to.be.within(417 * 0.95, 417 * 1.05); expect(width).to.be.within(290 * 0.95 - 1, 290 * 1.05);
expect(svg).to.not.have.attr('style'); expect(svg).to.not.have.attr('style');
}); });
}); });
@@ -760,51 +760,6 @@ A ~~~ B
); );
}); });
it('3258: Should render subgraphs with main graph nodeSpacing and rankSpacing', () => {
imgSnapshotTest(
`---
title: Subgraph nodeSpacing and rankSpacing example
---
flowchart LR
X --> Y
subgraph X
direction LR
A
C
end
subgraph Y
B
D
end
`,
{ flowchart: { nodeSpacing: 1, rankSpacing: 1 } }
);
});
it('3258: Should render subgraphs with large nodeSpacing and rankSpacing', () => {
imgSnapshotTest(
`---
title: Subgraph nodeSpacing and rankSpacing example
config:
flowchart:
nodeSpacing: 250
rankSpacing: 250
---
flowchart LR
X --> Y
subgraph X
direction LR
A
C
end
subgraph Y
B
D
end
`
);
});
describe('Markdown strings flowchart (#4220)', () => { describe('Markdown strings flowchart (#4220)', () => {
describe('html labels', () => { describe('html labels', () => {
it('With styling and classes', () => { it('With styling and classes', () => {
@@ -949,18 +904,6 @@ end
); );
}); });
}); });
it('should not auto wrap when markdownAutoWrap is false', () => {
imgSnapshotTest(
`flowchart TD
angular_velocity["\`**angular_velocity**
*angular_displacement / duration*
[rad/s, 1/s]
{vector}\`"]
frequency["frequency\n(1 / period_duration)\n[Hz, 1/s]"]`,
{ markdownAutoWrap: false }
);
});
}); });
describe('Subgraph title margins', () => { describe('Subgraph title margins', () => {
it('Should render subgraphs with title margins set (LR)', () => { it('Should render subgraphs with title margins set (LR)', () => {
@@ -1047,32 +990,7 @@ end
A --lb3--> TOP --lb4--> B A --lb3--> TOP --lb4--> B
B1 --lb5--> B2 B1 --lb5--> B2
`, `,
{ { flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } },
}
);
});
it('Should render self-loops', () => {
imgSnapshotTest(
`flowchart
A --> A
subgraph B
B1 --> B1
end
subgraph C
subgraph C1
C2 --> C2
subgraph D
D1 --> D1
end
D --> D
end
C1 --> C1
end
`,
{
flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } },
}
); );
}); });
}); });

View File

@@ -733,7 +733,7 @@ describe('Graph', () => {
}); });
it('38: should render a flowchart when useMaxWidth is true (default)', () => { it('38: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph( renderGraph(
`flowchart TD `graph TD
A[Christmas] -->|Get money| B(Go shopping) A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think} B --> C{Let me think}
C -->|One| D[Laptop] C -->|One| D[Laptop]
@@ -751,7 +751,7 @@ describe('Graph', () => {
const style = svg.attr('style'); const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/); expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.be.within(446 * 0.9, 446 * 1.1); expect(maxWidthValue).to.be.within(300 * 0.9, 300 * 1.1);
}); });
}); });
it('39: should render a flowchart when useMaxWidth is false', () => { it('39: should render a flowchart when useMaxWidth is false', () => {
@@ -770,7 +770,7 @@ describe('Graph', () => {
const width = parseFloat(svg.attr('width')); const width = parseFloat(svg.attr('width'));
// use within because the absolute value can be slightly different depending on the environment ±10% // use within because the absolute value can be slightly different depending on the environment ±10%
// expect(height).to.be.within(446 * 0.95, 446 * 1.05); // expect(height).to.be.within(446 * 0.95, 446 * 1.05);
expect(width).to.be.within(446 * 0.9, 446 * 1.1); expect(width).to.be.within(300 * 0.9, 300 * 1.1);
expect(svg).to.not.have.attr('style'); expect(svg).to.not.have.attr('style');
}); });
}); });
@@ -905,16 +905,13 @@ graph TD
it('67: should be able to style default node independently', () => { it('67: should be able to style default node independently', () => {
imgSnapshotTest( imgSnapshotTest(
` `
flowchart TD flowchart TD
classDef default fill:#a34 classDef default fill:#a34
hello --> default hello --> default
style default stroke:#000,stroke-width:4px style default stroke:#000,stroke-width:4px
`, `,
{ { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
flowchart: { htmlLabels: true },
securityLevel: 'loose',
}
); );
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More