diff --git a/.build/common.ts b/.build/common.ts
index 274977fa2..86ccd15d1 100644
--- a/.build/common.ts
+++ b/.build/common.ts
@@ -1,3 +1,9 @@
+export interface PackageOptions {
+ name: string;
+ packageName: string;
+ file: string;
+}
+
/**
* Shared common options for both ESBuild and Vite
*/
@@ -22,9 +28,9 @@ export const packageOptions = {
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
- 'mermaid-flowchart-elk': {
- name: 'mermaid-flowchart-elk',
- packageName: 'mermaid-flowchart-elk',
- file: 'detector.ts',
+ 'mermaid-layout-elk': {
+ name: 'mermaid-layout-elk',
+ packageName: 'mermaid-layout-elk',
+ file: 'layouts.ts',
},
-} as const;
+} as const satisfies Record
- đ Documentation | đ Getting Started | đ CDN | đ Join Us + đ Documentation | đ Getting Started | đ CDN | đ Join Us
įŽäŊ䏿
@@ -33,8 +33,10 @@ Try Live Editor previews of future releases:
@@ -42,7 +44,7 @@ Try Live Editor previews of future releases:
+
## Table of content
@@ -81,6 +83,10 @@ 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).
+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.
+
+[](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.
@@ -89,6 +95,10 @@ In our release process we rely heavily on visual regression tests using [applito
+## Mermaid AI Bot
+
+[Mermaid](https://codeparrot.ai/oracle?owner=mermaid-js&repo=mermaid) Bot will help you understand this repository better. You can ask for code examples, installation guide, debugging help and much more.
+
## Examples
**The following are some examples of the diagrams, charts and graphs that can be made using Mermaid. Click here to jump into the [text syntax](https://mermaid.js.org/intro/syntax-reference.html).**
@@ -247,6 +257,34 @@ pie
### Git graph [experimental - live editor]
+```
+gitGraph
+ commit
+ commit
+ branch develop
+ checkout develop
+ commit
+ commit
+ checkout main
+ merge develop
+ commit
+ commit
+```
+
+```mermaid
+gitGraph
+ commit
+ commit
+ branch develop
+ checkout develop
+ commit
+ commit
+ checkout main
+ merge develop
+ commit
+ commit
+```
+
### Bar chart (using gantt chart) [docs - live editor]
```
@@ -413,7 +451,7 @@ For public sites, it can be precarious to retrieve text from users on the intern
As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing javascript in the code from being executed. This is a great step forward for better security.
-_Unfortunately you can not have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code._
+_Unfortunately you cannot have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code._
## Reporting vulnerabilities
@@ -429,7 +467,7 @@ A quick note from Knut Sveidqvist:
>
> _Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017._
>
-> _Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!_
+> _Thank you to the ever-growing list of [contributors](https://github.com/mermaid-js/mermaid/graphs/contributors) that brought the project this far!_
---
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 942f54bff..cd44e56b6 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -15,7 +15,7 @@ Mermaid
åŽæļįŧčžå¨!
- đ ææĄŖ | đ å Ĩé¨ | đ CDN | đ å å ĨæäģŦ + đ ææĄŖ | đ å Ĩé¨ | đ CDN | đ å å ĨæäģŦ
English
@@ -34,7 +34,7 @@ Mermaid
[](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop)
[](https://www.jsdelivr.com/package/npm/mermaid)
[](https://www.npmjs.com/package/mermaid)
-[](https://discord.gg/AgrbSrBer3)
+[](https://discord.gg/sKeNQX4Wtj)
[](https://twitter.com/mermaidjs_)
@@ -43,13 +43,13 @@ Mermaid
**æč°ĸææåä¸čŋæĨæäē¤ PRīŧč§ŖįįéŽįäēēäģŦ! đ**
-
+
## å
ŗäē Mermaid
-Mermaid æ¯ä¸ä¸Ēåēäē Javascript įåžčĄ¨įģåļåˇĨå
ˇīŧéčŋč§Ŗæįąģ Markdown įææŦ蝿ŗæĨåŽį°åžčĄ¨įååģēå卿äŋŽæšãMermaid č¯įįä¸ģčĻįŽįæ¯čŽŠææĄŖįæ´æ°čŊå¤åæļčˇä¸åŧåčŋåēĻã
+Mermaid æ¯ä¸ä¸Ēåēäē JavaScript įåžčĄ¨įģåļåˇĨå
ˇīŧéčŋč§Ŗæįąģ Markdown įææŦ蝿ŗæĨåŽį°åžčĄ¨įååģēå卿äŋŽæšãMermaid č¯įįä¸ģčĻįŽįæ¯čŽŠææĄŖįæ´æ°čŊå¤åæļčˇä¸åŧåčŋåēĻã
> Doc-Rot æ¯ Mermaid č´åäēč§Ŗåŗįä¸ä¸Ēéžéĸã
@@ -358,7 +358,7 @@ _åžä¸åš¸įæ¯īŧéąŧä¸įæä¸å¯å
ŧåžīŧå¨čŋä¸Ēå翝ä¸åŽæåŗįå¨
> _įšåĢæč°ĸ [d3](https://d3js.org/) å [dagre-d3](https://github.com/cpettitt/dagre-d3) čŋ两ä¸Ēäŧį§į饚įŽīŧåŽäģŦæäžäēåžåŊĸå¸åąåįģåžåˇĨå
ˇåēīŧ_ > _åæ ˇæč°ĸ [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) æäžäēæļåēåžč¯æŗįäŊŋį¨ã æč°ĸ Jessica Peter æäžäēįįšåžæ¸˛æįįĩæã_ > _æč°ĸ [Tyler Long](https://github.com/tylerlong) äģ 2017 åš´åæåŧå§æä¸ēäē饚įŽįåäŊč
ã_
>
-> _æč°ĸčļæĨčļå¤į [č´ĄįŽč
äģŦ](https://github.com/knsv/mermaid/graphs/contributors)īŧæ˛ĄæäŊ äģŦīŧå°ąæ˛Ąæčŋä¸Ē饚įŽįäģ夊īŧ_
+> _æč°ĸčļæĨčļå¤į [č´ĄįŽč
äģŦ](https://github.com/mermaid-js/mermaid/graphs/contributors)īŧæ˛ĄæäŊ äģŦīŧå°ąæ˛Ąæčŋä¸Ē饚įŽįäģ夊īŧ_
---
diff --git a/cypress.config.ts b/cypress.config.ts
index 4182d92a8..50ea940e9 100644
--- a/cypress.config.ts
+++ b/cypress.config.ts
@@ -1,7 +1,10 @@
-import { defineConfig } from 'cypress';
-import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
-import coverage from '@cypress/code-coverage/task';
import eyesPlugin from '@applitools/eyes-cypress';
+import { registerArgosTask } from '@argos-ci/cypress/task';
+import coverage from '@cypress/code-coverage/task.js';
+import { defineConfig } from 'cypress';
+import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin.js';
+import cypressSplit from 'cypress-split';
+
export default eyesPlugin(
defineConfig({
projectId: 'n2sma2',
@@ -11,16 +14,22 @@ export default eyesPlugin(
specPattern: 'cypress/integration/**/*.{js,ts}',
setupNodeEvents(on, config) {
coverage(on, config);
+ cypressSplit(on, config);
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1');
}
return launchOptions;
});
- addMatchImageSnapshotPlugin(on, config);
// copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false;
+ config.env.useArgos = process.env.RUN_VISUAL_TEST === 'true';
+ if (config.env.useArgos) {
+ registerArgosTask(on, config);
+ } else {
+ addMatchImageSnapshotPlugin(on, config);
+ }
// do not forget to return the changed config object!
return config;
},
diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts
index aed5d7973..81b7036af 100644
--- a/cypress/helpers/util.ts
+++ b/cypress/helpers/util.ts
@@ -29,13 +29,14 @@ export const mermaidUrl = (
options: CypressMermaidConfig,
api: boolean
): string => {
+ options.handDrawnSeed = 1;
const codeObject: CodeObject = {
code: graphStr,
mermaid: options,
};
const objStr: string = JSON.stringify(codeObject);
let url = `http://localhost:9000/e2e.html?graph=${utf8ToB64(objStr)}`;
- if (api) {
+ if (api && typeof graphStr === 'string') {
url = `http://localhost:9000/xss.html?graph=${graphStr}`;
}
@@ -54,16 +55,15 @@ export const imgSnapshotTest = (
): void => {
const options: CypressMermaidConfig = {
..._options,
- fontFamily: _options.fontFamily || 'courier',
+ fontFamily: _options.fontFamily ?? 'courier',
// @ts-ignore TODO: Fix type of fontSize
- fontSize: _options.fontSize || '16px',
+ fontSize: _options.fontSize ?? '16px',
sequence: {
- ...(_options.sequence || {}),
+ ...(_options.sequence ?? {}),
actorFontFamily: 'courier',
- noteFontFamily:
- _options.sequence && _options.sequence.noteFontFamily
- ? _options.sequence.noteFontFamily
- : 'courier',
+ noteFontFamily: _options.sequence?.noteFontFamily
+ ? _options.sequence.noteFontFamily
+ : 'courier',
messageFontFamily: 'courier',
},
};
@@ -74,7 +74,7 @@ export const imgSnapshotTest = (
export const urlSnapshotTest = (
url: string,
- options: CypressMermaidConfig,
+ options: CypressMermaidConfig = {},
_api = false,
validation?: any
): void => {
@@ -95,18 +95,7 @@ export const openURLAndVerifyRendering = (
options: CypressMermaidConfig,
validation?: any
): void => {
- const useAppli: boolean = Cypress.env('useAppli');
- const name: string = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
-
- if (useAppli) {
- cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
- cy.eyesOpen({
- appName: 'Mermaid',
- testName: name,
- batchName: Cypress.spec.name,
- batchId: batchId + Cypress.spec.name,
- });
- }
+ const name: string = (options.name ?? cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
@@ -116,12 +105,37 @@ export const openURLAndVerifyRendering = (
cy.get('svg').should(validation);
}
+ verifyScreenshot(name);
+};
+
+export const verifyScreenshot = (name: string): void => {
+ const useAppli: boolean = Cypress.env('useAppli');
+ const useArgos: boolean = Cypress.env('useArgos');
+
if (useAppli) {
+ cy.log(`Opening eyes ${Cypress.spec.name} --- ${name}`);
+ cy.eyesOpen({
+ appName: 'Mermaid',
+ testName: name,
+ batchName: Cypress.spec.name,
+ batchId: batchId + Cypress.spec.name,
+ });
cy.log(`Check eyes ${Cypress.spec.name}`);
cy.eyesCheckWindow('Click!');
cy.log(`Closing eyes ${Cypress.spec.name}`);
cy.eyesClose();
+ } else if (useArgos) {
+ cy.argosScreenshot(name, {
+ threshold: 0.01,
+ });
} else {
cy.matchImageSnapshot(name);
}
};
+
+export const verifyNumber = (value: number, expected: number, deltaPercent = 10): void => {
+ expect(value).to.be.within(
+ expected * (1 - deltaPercent / 100),
+ expected * (1 + deltaPercent / 100)
+ );
+};
diff --git a/cypress/integration/other/configuration.spec.js b/cypress/integration/other/configuration.spec.js
index 544eab40f..ad6b21e29 100644
--- a/cypress/integration/other/configuration.spec.js
+++ b/cypress/integration/other/configuration.spec.js
@@ -1,4 +1,4 @@
-import { renderGraph } from '../../helpers/util.ts';
+import { renderGraph, verifyScreenshot } from '../../helpers/util.ts';
describe('Configuration', () => {
describe('arrowMarkerAbsolute', () => {
it('should handle default value false of arrowMarkerAbsolute', () => {
@@ -119,8 +119,7 @@ describe('Configuration', () => {
const url = 'http://localhost:9000/regression/issue-1874.html';
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
- cy.get('svg').should('be.visible');
- cy.matchImageSnapshot(
+ verifyScreenshot(
'configuration.spec-should-not-taint-initial-configuration-when-using-multiple-directives'
);
});
@@ -145,7 +144,7 @@ describe('Configuration', () => {
// none of the diagrams should be error diagrams
expect($svg).to.not.contain('Syntax error');
});
- cy.matchImageSnapshot(
+ verifyScreenshot(
'configuration.spec-should-not-render-error-diagram-if-suppressErrorRendering-is-set'
);
});
@@ -162,7 +161,7 @@ describe('Configuration', () => {
// some of the diagrams should be error diagrams
expect($svg).to.contain('Syntax error');
});
- cy.matchImageSnapshot(
+ verifyScreenshot(
'configuration.spec-should-render-error-diagram-if-suppressErrorRendering-is-not-set'
);
});
diff --git a/cypress/integration/other/flowchart-elk.spec.js b/cypress/integration/other/flowchart-elk.spec.js
deleted file mode 100644
index 22a6efc0f..000000000
--- a/cypress/integration/other/flowchart-elk.spec.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts';
-
-describe('Flowchart elk', () => {
- it('should use dagre as fallback', () => {
- urlSnapshotTest('http://localhost:9000/flow-elk.html', {
- name: 'flow-elk fallback to dagre',
- });
- });
- it('should allow overriding with external package', () => {
- urlSnapshotTest('http://localhost:9000/flow-elk.html?elk=true', {
- name: 'flow-elk overriding dagre with elk',
- });
- });
-});
diff --git a/cypress/integration/other/interaction.spec.js b/cypress/integration/other/interaction.spec.js
index 857141b5b..e57701fd9 100644
--- a/cypress/integration/other/interaction.spec.js
+++ b/cypress/integration/other/interaction.spec.js
@@ -20,7 +20,7 @@ describe('Interaction', () => {
});
it('Graph: should handle a click on a node with a bound url', () => {
- // When there is a URL, cy.contains selects the a tag instead of the span. The .node is a child of a, so we have to use find instead of parent.
+ // When there is a URL, `cy.contains()` selects the `a` tag instead of the `span` tag. The .node is a child of `a`, so we have to use `find()` instead of `parent`.
cy.contains('URLTest1').find('.node').click();
cy.location().should(({ href }) => {
expect(href).to.eq('http://localhost:9000/empty.html');
@@ -146,7 +146,7 @@ describe('Interaction', () => {
});
});
- describe('Interaction - security level other, missspelling', () => {
+ describe('Interaction - security level other, misspelling', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/click_security_other.html');
});
diff --git a/cypress/integration/other/xss.spec.js b/cypress/integration/other/xss.spec.js
index 678040f98..1e51d2f23 100644
--- a/cypress/integration/other/xss.spec.js
+++ b/cypress/integration/other/xss.spec.js
@@ -10,7 +10,6 @@ describe('XSS', () => {
cy.wait(1000).then(() => {
cy.get('.mermaid').should('exist');
});
- cy.get('svg');
});
it('should not allow tags in the css', () => {
@@ -137,4 +136,9 @@ describe('XSS', () => {
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
+ it('should sanitize backticks block diagram labels properly', () => {
+ cy.visit('http://localhost:9000/xss25.html');
+ cy.wait(1000);
+ cy.get('#the-malware').should('not.exist');
+ });
});
diff --git a/cypress/integration/rendering/appli.spec.js b/cypress/integration/rendering/appli.spec.js
index 5def96815..51eeb657e 100644
--- a/cypress/integration/rendering/appli.spec.js
+++ b/cypress/integration/rendering/appli.spec.js
@@ -11,6 +11,27 @@ 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 () {
// // Navigate to the url we want to test
// // âī¸ Note to see visual bugs, run the test using the above URL for the 1st run.
diff --git a/cypress/integration/rendering/architecture.spec.ts b/cypress/integration/rendering/architecture.spec.ts
new file mode 100644
index 000000000..997a6654e
--- /dev/null
+++ b/cypress/integration/rendering/architecture.spec.ts
@@ -0,0 +1,252 @@
+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 a simple architecture diagram with titleAndAccessibilities', () => {
+ imgSnapshotTest(
+ `architecture-beta
+ title Simple Architecture Diagram
+ accTitle: Accessibility Title
+ accDescr: Accessibility Description
+ 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
+
+ db:L -- R:server
+ disk1:T -- B:server
+ disk2:T -- B:db
+ `
+ );
+ });
+ 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}
+ `
+ );
+ });
+
+ it('should render an architecture diagram with a reasonable height', () => {
+ imgSnapshotTest(
+ `architecture-beta
+ group federated(cloud)[Federated Environment]
+ service server1(server)[System] in federated
+ service edge(server)[Edge Device] in federated
+ server1:R -- L:edge
+
+ group on_prem(cloud)[Hub]
+ service firewall(server)[Firewall Device] in on_prem
+ service server(server)[Server] in on_prem
+ firewall:R -- L:server
+
+ service db1(database)[db1] in on_prem
+ service db2(database)[db2] in on_prem
+ service db3(database)[db3] in on_prem
+ service db4(database)[db4] in on_prem
+ service db5(database)[db5] in on_prem
+ service db6(database)[db6] in on_prem
+
+ junction mid in on_prem
+ server:B -- T:mid
+
+ junction 1Leftofmid in on_prem
+ 1Leftofmid:R -- L:mid
+ 1Leftofmid:B -- T:db1
+
+ junction 2Leftofmid in on_prem
+ 2Leftofmid:R -- L:1Leftofmid
+ 2Leftofmid:B -- T:db2
+
+ junction 3Leftofmid in on_prem
+ 3Leftofmid:R -- L:2Leftofmid
+ 3Leftofmid:B -- T:db3
+
+ junction 1RightOfMid in on_prem
+ mid:R -- L:1RightOfMid
+ 1RightOfMid:B -- T:db4
+
+ junction 2RightOfMid in on_prem
+ 1RightOfMid:R -- L:2RightOfMid
+ 2RightOfMid:B -- T:db5
+
+ junction 3RightOfMid in on_prem
+ 2RightOfMid:R -- L:3RightOfMid
+ 3RightOfMid:B -- T:db6
+
+ edge:R -- L:firewall
+ `
+ );
+ });
+});
+
+// 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');
+ });
+});
diff --git a/cypress/integration/rendering/block.spec.js b/cypress/integration/rendering/block.spec.js
index 9d62c642d..589a30fde 100644
--- a/cypress/integration/rendering/block.spec.js
+++ b/cypress/integration/rendering/block.spec.js
@@ -14,7 +14,7 @@ describe('Block diagram', () => {
);
});
- it('BL2: should handle colums statement in sub-blocks', () => {
+ it('BL2: should handle columns statement in sub-blocks', () => {
imgSnapshotTest(
`block-beta
id1["Hello"]
@@ -30,7 +30,7 @@ describe('Block diagram', () => {
);
});
- it('BL3: should align block widths and handle colums statement in sub-blocks', () => {
+ it('BL3: should align block widths and handle columns statement in sub-blocks', () => {
imgSnapshotTest(
`block-beta
block
@@ -46,7 +46,7 @@ describe('Block diagram', () => {
);
});
- it('BL4: should align block widths and handle colums statements in deeper sub-blocks then 1 level', () => {
+ it('BL4: should align block widths and handle columns statements in deeper sub-blocks then 1 level', () => {
imgSnapshotTest(
`block-beta
columns 1
@@ -66,7 +66,7 @@ describe('Block diagram', () => {
);
});
- it('BL5: should align block widths and handle colums statements in deeper sub-blocks then 1 level (alt)', () => {
+ it('BL5: should align block widths and handle columns statements in deeper sub-blocks then 1 level (alt)', () => {
imgSnapshotTest(
`block-beta
columns 1
@@ -236,7 +236,7 @@ describe('Block diagram', () => {
);
});
- it('BL16: width alignment - blocks shold be equal in width', () => {
+ it('BL17: width alignment - blocks should be equal in width', () => {
imgSnapshotTest(
`block-beta
A("This is the text")
@@ -247,7 +247,7 @@ describe('Block diagram', () => {
);
});
- it('BL17: block types 1 - square, rounded and circle', () => {
+ it('BL18: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@@ -258,7 +258,7 @@ describe('Block diagram', () => {
);
});
- it('BL18: block types 2 - odd, diamond and hexagon', () => {
+ it('BL19: block types 2 - odd, diamond and hexagon', () => {
imgSnapshotTest(
`block-beta
A>"rect_left_inv_arrow"]
@@ -269,7 +269,7 @@ describe('Block diagram', () => {
);
});
- it('BL19: block types 3 - stadium', () => {
+ it('BL20: block types 3 - stadium', () => {
imgSnapshotTest(
`block-beta
A(["stadium"])
@@ -278,7 +278,7 @@ describe('Block diagram', () => {
);
});
- it('BL20: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
+ it('BL21: block types 4 - lean right, lean left, trapezoid and inv trapezoid', () => {
imgSnapshotTest(
`block-beta
A[/"lean right"/]
@@ -290,7 +290,7 @@ describe('Block diagram', () => {
);
});
- it('BL21: block types 1 - square, rounded and circle', () => {
+ it('BL22: block types 1 - square, rounded and circle', () => {
imgSnapshotTest(
`block-beta
A["square"]
@@ -301,7 +301,7 @@ describe('Block diagram', () => {
);
});
- it('BL22: sizing - it should be possible to make a block wider', () => {
+ it('BL23: sizing - it should be possible to make a block wider', () => {
imgSnapshotTest(
`block-beta
A("rounded"):2
@@ -312,7 +312,7 @@ describe('Block diagram', () => {
);
});
- it('BL23: sizing - it should be possible to make a composite block wider', () => {
+ it('BL24: sizing - it should be possible to make a composite block wider', () => {
imgSnapshotTest(
`block-beta
block:2
@@ -324,7 +324,7 @@ describe('Block diagram', () => {
);
});
- it('BL24: block in the middle with space on each side', () => {
+ it('BL25: block in the middle with space on each side', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -335,7 +335,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL25: space and an edge', () => {
+ it('BL26: space and an edge', () => {
imgSnapshotTest(
`block-beta
columns 5
@@ -345,7 +345,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL26: block sizes for regular blocks', () => {
+ it('BL27: block sizes for regular blocks', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -354,7 +354,7 @@ describe('Block diagram', () => {
{}
);
});
- it('BL27: composite block with a set width - f should use the available space', () => {
+ it('BL28: composite block with a set width - f should use the available space', () => {
imgSnapshotTest(
`block-beta
columns 3
@@ -363,11 +363,12 @@ describe('Block diagram', () => {
f
end
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(
`block-beta
columns 3
@@ -379,7 +380,7 @@ describe('Block diagram', () => {
h
i
j
- `,
+ `,
{}
);
});
diff --git a/cypress/integration/rendering/c4.spec.js b/cypress/integration/rendering/c4.spec.js
index 59af6504b..00e71adec 100644
--- a/cypress/integration/rendering/c4.spec.js
+++ b/cypress/integration/rendering/c4.spec.js
@@ -1,7 +1,7 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe('C4 diagram', () => {
- it('should render a simple C4Context diagram', () => {
+ it('C4.1 should render a simple C4Context diagram', () => {
imgSnapshotTest(
`
C4Context
@@ -30,9 +30,8 @@ describe('C4 diagram', () => {
`,
{}
);
- cy.get('svg');
});
- it('should render a simple C4Container diagram', () => {
+ it('C4.2 should render a simple C4Container diagram', () => {
imgSnapshotTest(
`
C4Container
@@ -50,9 +49,8 @@ describe('C4 diagram', () => {
`,
{}
);
- cy.get('svg');
});
- it('should render a simple C4Component diagram', () => {
+ it('C4.3 should render a simple C4Component diagram', () => {
imgSnapshotTest(
`
C4Component
@@ -69,9 +67,8 @@ describe('C4 diagram', () => {
`,
{}
);
- cy.get('svg');
});
- it('should render a simple C4Dynamic diagram', () => {
+ it('C4.4 should render a simple C4Dynamic diagram', () => {
imgSnapshotTest(
`
C4Dynamic
@@ -93,9 +90,8 @@ describe('C4 diagram', () => {
`,
{}
);
- cy.get('svg');
});
- it('should render a simple C4Deployment diagram', () => {
+ it('C4.5 should render a simple C4Deployment diagram', () => {
imgSnapshotTest(
`
C4Deployment
@@ -117,6 +113,5 @@ describe('C4 diagram', () => {
`,
{}
);
- cy.get('svg');
});
});
diff --git a/cypress/integration/rendering/classDiagram-elk-v3.spec.js b/cypress/integration/rendering/classDiagram-elk-v3.spec.js
new file mode 100644
index 000000000..ee6ca0b2b
--- /dev/null
+++ b/cypress/integration/rendering/classDiagram-elk-v3.spec.js
@@ -0,0 +1,1037 @@
+import { imgSnapshotTest } from '../../helpers/util.ts';
+describe('Class diagram V3 ELK', () => {
+ it('ELK-0: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+
+ classA -- classB : Inheritance
+ classA -- classC : link
+ classC -- classD : link
+ classB -- classD
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-1: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-1.1: should render a simple class diagram without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-2: should render a simple class diagrams with cardinality', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-2.1: should render a simple class diagrams with cardinality without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-2.2 should render a simple class diagram with different visibilities', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK-2.3 should render a simple class diagram with different visibilities without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-3: should render multiple class diagrams', () => {
+ imgSnapshotTest(
+ [
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ ],
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-4: should render a simple class diagram with comments', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ %% this is a comment
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-5: should render a simple class diagram with abstract method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-5.1: should render a simple class diagram with abstract method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-6: should render a simple class diagram with static method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-6.1: should render a simple class diagram with static method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-7: should render a simple class diagram with Generic class', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-7.1: should render a simple class diagram with Generic class without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-8: should render a simple class diagram with Generic class and relations', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-9: should render a simple class diagram with clickable link', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ link Class01 "google.com" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-10: should render a simple class diagram with clickable callback', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ callback Class01 "functionCall" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-11: should render a simple class diagram with return type on method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-11.1: should render a simple class diagram with return type on method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-12: should render a simple class diagram with generic types', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-12.1: should render a simple class diagram with generic types without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+
+ it('ELK-13: should render a simple class diagram with css classes applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+
+ cssClass "Class10" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-14: should render a simple class diagram with css classes applied directly', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::exClass2 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-15: should render a simple class diagram with css classes applied two multiple classes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ class Class20
+
+ cssClass "Class10, class20" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-16a: should render a simple class diagram with static field', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Foo {
+ +String bar$
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-16b: should handle the direction statement with TB', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction TB
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK-17a: should handle the direction statement with BT', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction BT
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK-17b: should handle the direction statement with RL', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction RL
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-18a: should handle the direction statement with LR', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction LR
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-18b: should render a simple class diagram with notes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ class Class10 {
+ int id
+ size()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK-1433: should render a simple class with a title', () => {
+ imgSnapshotTest(
+ `---
+title: simple class diagram
+---
+classDiagram
+class Class10
+`,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK: should render a class with text label', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK: should render two classes with text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ class C2["Class 2 with chars @?"]
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a class with a text label, members and annotation', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"] {
+ <<interface>>
+ +member1
+ }
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render multiple classes with same text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class with text label"]
+ class C2["Class with text label"]
+ class C3["Class with text label"]
+ C1 --> C2
+ C3 ..> C2
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render classes with different text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["OneWord"]
+ class C2["With, Comma"]
+ class C3["With (Brackets)"]
+ class C4["With [Brackets]"]
+ class C5["With {Brackets}"]
+ class C7["With 1 number"]
+ class C8["With . period..."]
+ class C9["With - dash"]
+ class C10["With _ underscore"]
+ class C11["With ' single quote"]
+ class C12["With ~!@#$%^&*()_+=-/?"]
+ class C13["With Città foreign language"]
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+
+ it('ELK: should render classLabel if class has already been defined earlier', () => {
+ imgSnapshotTest(
+ `classDiagram
+ Animal <|-- Duck
+ class Duck["Duck with text label"]
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should add classes namespaces', function () {
+ imgSnapshotTest(
+ `
+ classDiagram
+ namespace Namespace1 {
+ class C1
+ class C2
+ }
+ C1 --> C2
+ class C3
+ class C4
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with no members', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with no members if hideEmptyMembersBox is enabled', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, class: { htmlLabels: true, hideEmptyMembersBox: true }, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with no attributes, only methods', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +swim()
+ +quack()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with no methods, only attributes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +String beakColor
+ +int age
+ +float weight
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with style definition', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with style definition without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with classDef definitions', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with classDefs being applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with classDefs being applied without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with markdown styling', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with markdown styling without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn', layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with styles and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn', layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with styles and the handDrawn look without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn', layout: 'elk' }
+ );
+ });
+ it('ELK: should render a full class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn', layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with a custom theme', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+ it('ELK: should render a simple class diagram with a custom theme and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn', layout: 'elk' }
+ );
+ });
+ it('ELK: should render a full class diagram using elk', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true, layout: 'elk' }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js b/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js
new file mode 100644
index 000000000..32a82c089
--- /dev/null
+++ b/cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js
@@ -0,0 +1,1041 @@
+import { imgSnapshotTest } from '../../helpers/util.ts';
+describe('Class diagram V3 HD', () => {
+ it('HD-0: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+
+ classA -- classB : Inheritance
+ classA -- classC : link
+ classC -- classD : link
+ classB -- classD
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-1: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-1.1: should render a simple class diagram without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-2: should render a simple class diagrams with cardinality', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-2.1: should render a simple class diagrams with cardinality without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-2.2 should render a simple class diagram with different visibilities', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD-2.3 should render a simple class diagram with different visibilities without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-3: should render multiple class diagrams', () => {
+ imgSnapshotTest(
+ [
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ ],
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-4: should render a simple class diagram with comments', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ %% this is a comment
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-5: should render a simple class diagram with abstract method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-5.1: should render a simple class diagram with abstract method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-6: should render a simple class diagram with static method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-6.1: should render a simple class diagram with static method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-7: should render a simple class diagram with Generic class', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-7.1: should render a simple class diagram with Generic class without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-8: should render a simple class diagram with Generic class and relations', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-9: should render a simple class diagram with clickable link', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ link Class01 "google.com" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-10: should render a simple class diagram with clickable callback', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ callback Class01 "functionCall" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-11: should render a simple class diagram with return type on method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-11.1: should render a simple class diagram with return type on method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-12: should render a simple class diagram with generic types', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-12.1: should render a simple class diagram with generic types without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-13: should render a simple class diagram with css classes applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+
+ cssClass "Class10" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-14: should render a simple class diagram with css classes applied directly', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::exClass2 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-15: should render a simple class diagram with css classes applied two multiple classes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ class Class20
+
+ cssClass "Class10, class20" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-16a: should render a simple class diagram with static field', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Foo {
+ +String bar$
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-16b: should handle the direction statement with TB', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction TB
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD-17a: should handle the direction statement with BT', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction BT
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD-17b: should handle the direction statement with RL', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction RL
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-18a: should handle the direction statement with LR', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction LR
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-18b: should render a simple class diagram with notes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ class Class10 {
+ int id
+ size()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD-1433: should render a simple class with a title', () => {
+ imgSnapshotTest(
+ `---
+title: simple class diagram
+---
+classDiagram
+class Class10
+`,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD: should render a class with text label', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD: should render two classes with text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ class C2["Class 2 with chars @?"]
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a class with a text label, members and annotation', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"] {
+ <<interface>>
+ +member1
+ }
+ C1 --> C2`,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render multiple classes with same text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+class C1["Class with text label"]
+class C2["Class with text label"]
+class C3["Class with text label"]
+C1 --> C2
+C3 ..> C2
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render classes with different text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+class C1["OneWord"]
+class C2["With, Comma"]
+class C3["With (Brackets)"]
+class C4["With [Brackets]"]
+class C5["With {Brackets}"]
+class C7["With 1 number"]
+class C8["With . period..."]
+class C9["With - dash"]
+class C10["With _ underscore"]
+class C11["With ' single quote"]
+class C12["With ~!@#$%^&*()_+=-/?"]
+class C13["With Città foreign language"]
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+
+ it('HD: should render classLabel if class has already been defined earlier', () => {
+ imgSnapshotTest(
+ `classDiagram
+ Animal <|-- Duck
+ class Duck["Duck with text label"]
+`,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should add classes namespaces', function () {
+ imgSnapshotTest(
+ `
+ classDiagram
+ namespace Namespace1 {
+ class C1
+ class C2
+ }
+ C1 --> C2
+ class C3
+ class C4
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with no members', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with no members if hideEmptyMembersBox is enabled', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, class: { htmlLabels: true, hideEmptyMembersBox: true }, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with no attributes, only methods', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +swim()
+ +quack()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with no methods, only attributes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +String beakColor
+ +int age
+ +float weight
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with style definition', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with style definition without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with classDef definitions', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with classDefs being applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with classDefs being applied without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with markdown styling', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with markdown styling without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with styles and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with styles and the handDrawn look without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a full class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with a custom theme', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a simple class diagram with a custom theme and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('HD: should render a full class diagram using elk', () => {
+ imgSnapshotTest(
+ `
+---
+ config:
+ layout: elk
+---
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js
index 20a1aea0a..0c5dbc04b 100644
--- a/cypress/integration/rendering/classDiagram-v2.spec.js
+++ b/cypress/integration/rendering/classDiagram-v2.spec.js
@@ -76,7 +76,7 @@ describe('Class diagram V2', () => {
);
});
- it('should render a simple class diagram with different visibilities', () => {
+ it('2.1 should render a simple class diagram with different visibilities', () => {
imgSnapshotTest(
`
classDiagram-v2
@@ -93,7 +93,7 @@ describe('Class diagram V2', () => {
);
});
- it('should render multiple class diagrams', () => {
+ it('3: should render multiple class diagrams', () => {
imgSnapshotTest(
[
`
@@ -581,4 +581,63 @@ class C13["With Città foreign language"]
{ 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);
+ });
});
diff --git a/cypress/integration/rendering/classDiagram-v3.spec.js b/cypress/integration/rendering/classDiagram-v3.spec.js
new file mode 100644
index 000000000..626d6fcea
--- /dev/null
+++ b/cypress/integration/rendering/classDiagram-v3.spec.js
@@ -0,0 +1,1031 @@
+import { imgSnapshotTest } from '../../helpers/util.ts';
+describe('Class diagram V3', () => {
+ it('0: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+
+ classA -- classB : Inheritance
+ classA -- classC : link
+ classC -- classD : link
+ classB -- classD
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('1: should render a simple class diagram', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('1.1: should render a simple class diagram without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('2: should render a simple class diagrams with cardinality', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('2.1: should render a simple class diagrams with cardinality without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('2.2 should render a simple class diagram with different visibilities', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('2.3 should render a simple class diagram with different visibilities without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class01 : -privateMethod()
+ Class01 : +publicMethod()
+ Class01 : #protectedMethod()
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('3: should render multiple class diagrams', () => {
+ imgSnapshotTest(
+ [
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ `
+ classDiagram
+ Class01 "1" <|--|> "*" AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 "1" <--> "*" C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ ],
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('4: should render a simple class diagram with comments', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ %% this is a comment
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('5: should render a simple class diagram with abstract method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('5.1: should render a simple class diagram with abstract method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()*
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('6: should render a simple class diagram with static method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('6.1: should render a simple class diagram with static method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ Class01 : someMethod()$
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('7: should render a simple class diagram with Generic class', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('7.1: should render a simple class diagram with Generic class without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class01~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('8: should render a simple class diagram with Generic class and relations', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('9: should render a simple class diagram with clickable link', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ link Class01 "google.com" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('10: should render a simple class diagram with clickable callback', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ Class01~T~ <|-- AveryLongClass : Cool
+ Class03~T~ *-- Class04~T~
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class08 <--> C2: Cool label
+ class Class10~T~ {
+ <<service>>
+ int id
+ test()
+ }
+ callback Class01 "functionCall" "A Tooltip"
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('11: should render a simple class diagram with return type on method', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('11.1: should render a simple class diagram with return type on method without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ test(int[] ids) bool
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('12: should render a simple class diagram with generic types', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('12.1: should render a simple class diagram with generic types without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10~T~ {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+
+ it('13: should render a simple class diagram with css classes applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+
+ cssClass "Class10" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('14: should render a simple class diagram with css classes applied directly', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::exClass2 {
+ int[] id
+ List~int~ ids
+ test(List~int~ ids) List~bool~
+ testArray() bool[]
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ class Class20
+
+ cssClass "Class10, class20" exClass2
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('16a: should render a simple class diagram with static field', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Foo {
+ +String bar$
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('16b: should handle the direction statement with TB', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction TB
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('17a: should handle the direction statement with BT', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction BT
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('17b: should handle the direction statement with RL', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction RL
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('18a: should handle the direction statement with LR', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ direction LR
+ class Student {
+ -idCard : IdCard
+ }
+ class IdCard{
+ -id : int
+ -name : string
+ }
+ class Bike{
+ -id : int
+ -name : string
+ }
+ Student "1" --o "1" IdCard : carries
+ Student "1" --o "1" Bike : rides
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('18b: should render a simple class diagram with notes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ class Class10 {
+ int id
+ size()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+
+ it('1433: should render a simple class with a title', () => {
+ imgSnapshotTest(
+ `---
+title: simple class diagram
+---
+classDiagram
+class Class10
+`
+ );
+ });
+
+ it('should render a class with text label', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ C1 --> C2`
+ );
+ });
+
+ it('should render two classes with text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"]
+ class C2["Class 2 with chars @?"]
+ C1 --> C2`
+ );
+ });
+ it('should render a class with a text label, members and annotation', () => {
+ imgSnapshotTest(
+ `classDiagram
+ class C1["Class 1 with text label"] {
+ <<interface>>
+ +member1
+ }
+ C1 --> C2`
+ );
+ });
+ it('should render multiple classes with same text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+class C1["Class with text label"]
+class C2["Class with text label"]
+class C3["Class with text label"]
+C1 --> C2
+C3 ..> C2
+ `
+ );
+ });
+ it('should render classes with different text labels', () => {
+ imgSnapshotTest(
+ `classDiagram
+class C1["OneWord"]
+class C2["With, Comma"]
+class C3["With (Brackets)"]
+class C4["With [Brackets]"]
+class C5["With {Brackets}"]
+class C7["With 1 number"]
+class C8["With . period..."]
+class C9["With - dash"]
+class C10["With _ underscore"]
+class C11["With ' single quote"]
+class C12["With ~!@#$%^&*()_+=-/?"]
+class C13["With Città foreign language"]
+ `
+ );
+ });
+
+ it('should render classLabel if class has already been defined earlier', () => {
+ imgSnapshotTest(
+ `classDiagram
+ Animal <|-- Duck
+ class Duck["Duck with text label"]
+`
+ );
+ });
+ it('should add classes namespaces', function () {
+ imgSnapshotTest(
+ `
+ classDiagram
+ namespace Namespace1 {
+ class C1
+ class C2
+ }
+ C1 --> C2
+ class C3
+ class C4
+ `
+ );
+ });
+ it('should render a simple class diagram with no members', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with no members if hideEmptyMembersBox is enabled', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, class: { htmlLabels: true, hideEmptyMembersBox: true } }
+ );
+ });
+ it('should render a simple class diagram with no attributes, only methods', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +swim()
+ +quack()
+ }
+ `
+ );
+ });
+ it('should render a simple class diagram with no methods, only attributes', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Duck {
+ +String beakColor
+ +int age
+ +float weight
+ }
+ `
+ );
+ });
+ it('should render a simple class diagram with style definition', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with style definition without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+ it('should render a simple class diagram with classDef definitions', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with classDefs being applied', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with classDefs being applied without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10:::pink
+ cssClass "Class10" bold
+ classDef pink fill:#f9f
+ classDef bold stroke:#333,stroke-width:6px,color:#fff
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+ it('should render a simple class diagram with markdown styling', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with markdown styling without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10 {
+ +attribute *italic**
+ ~attribute **bold***
+ _italicmethod_()
+ __boldmethod__()
+ _+_swim_()a_
+ __+quack() test__
+ }
+ `,
+ { logLevel: 1, htmlLabels: false }
+ );
+ });
+ it('should render a simple class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('should render a simple class diagram with styles and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('should render a simple class diagram with styles and the handDrawn look without htmlLabels', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ class Class10
+ style Class10 fill:#f9f,stroke:#333,stroke-width:4px,color:white
+ `,
+ { logLevel: 1, htmlLabels: false, look: 'handDrawn' }
+ );
+ });
+ it('should render a full class diagram with the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('should render a simple class diagram with a custom theme', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+ it('should render a simple class diagram with a custom theme and the handDrawn look', () => {
+ imgSnapshotTest(
+ `
+ %%{
+ init: {
+ 'theme': 'base',
+ 'themeVariables': {
+ 'primaryColor': '#BB2528',
+ 'primaryTextColor': '#fff',
+ 'primaryBorderColor': '#7C0000',
+ 'lineColor': '#F83d29',
+ 'secondaryColor': '#006100',
+ 'tertiaryColor': '#fff'
+ }
+ }
+ }%%
+ classDiagram
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 *-- Class04
+ Class05 o-- Class06
+ Class07 .. Class08
+ Class09 --> C2 : Where am i?
+ Class09 --* C3
+ Class09 --|> Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ `,
+ { logLevel: 1, htmlLabels: true, look: 'handDrawn' }
+ );
+ });
+ it('should render a full class diagram using elk', () => {
+ imgSnapshotTest(
+ `
+---
+ config:
+ layout: elk
+---
+ classDiagram
+ note "I love this diagram!\nDo you love it?"
+ Class01 <|-- AveryLongClass : Cool
+ <<interface>> Class01
+ Class03 "1" *-- "*" Class04
+ Class05 "1" o-- "many" Class06
+ Class07 "1" .. "*" Class08
+ Class09 "1" --> "*" C2 : Where am i?
+ Class09 "*" --* "*" C3
+ Class09 "1" --|> "1" Class07
+ Class12 <|.. Class08
+ Class11 ..>Class12
+ Class07 : equals()
+ Class07 : Object[] elementData
+ Class01 : size()
+ Class01 : int chimp
+ Class01 : int gorilla
+ Class01 : -int privateChimp
+ Class01 : +int publicGorilla
+ Class01 : #int protectedMarmoset
+ Class08 <--> C2: Cool label
+ class Class10 {
+ <<service>>
+ int id
+ test()
+ }
+ note for Class10 "Cool class\nI said it's very cool class!"
+ `,
+ { logLevel: 1, htmlLabels: true }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js
index cab3649df..e7d201b5d 100644
--- a/cypress/integration/rendering/classDiagram.spec.js
+++ b/cypress/integration/rendering/classDiagram.spec.js
@@ -32,7 +32,6 @@ describe('Class diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('2: should render a simple class diagrams with cardinality', () => {
@@ -61,7 +60,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('3: should render a simple class diagram with different visibilities', () => {
@@ -79,7 +77,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('4: should render a simple class diagram with comments', () => {
@@ -109,7 +106,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('5: should render a simple class diagram with abstract method', () => {
@@ -121,7 +117,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('6: should render a simple class diagram with static method', () => {
@@ -133,7 +128,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('7: should render a simple class diagram with Generic class', () => {
@@ -153,7 +147,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('8: should render a simple class diagram with Generic class and relations', () => {
@@ -174,7 +167,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('9: should render a simple class diagram with clickable link', () => {
@@ -196,7 +188,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('10: should render a simple class diagram with clickable callback', () => {
@@ -218,7 +209,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('11: should render a simple class diagram with return type on method', () => {
@@ -233,7 +223,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('12: should render a simple class diagram with generic types', () => {
@@ -249,7 +238,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('13: should render a simple class diagram with css classes applied', () => {
@@ -267,7 +255,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('14: should render a simple class diagram with css classes applied directly', () => {
@@ -283,7 +270,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
@@ -298,7 +284,6 @@ describe('Class diagram', () => {
`,
{}
);
- cy.get('svg');
});
it('16: should render multiple class diagrams', () => {
@@ -351,7 +336,6 @@ describe('Class diagram', () => {
],
{}
);
- cy.get('svg');
});
// it('17: should render a class diagram when useMaxWidth is true (default)', () => {
@@ -421,7 +405,6 @@ describe('Class diagram', () => {
`,
{ logLevel: 1 }
);
- cy.get('svg');
});
it('should render class diagram with newlines in title', () => {
@@ -439,7 +422,6 @@ describe('Class diagram', () => {
+quack()
}
`);
- cy.get('svg');
});
it('should render class diagram with many newlines in title', () => {
@@ -447,7 +429,7 @@ describe('Class diagram', () => {
classDiagram
class \`This\nTitle\nHas\nMany\nNewlines\` {
+String Also
- -Stirng Many
+ -String Many
#int Members
+And()
-Many()
@@ -461,7 +443,7 @@ describe('Class diagram', () => {
classDiagram
class \`This\nTitle\nHas\nMany\nNewlines\` {
+String Also
- -Stirng Many
+ -String Many
#int Members
+And()
-Many()
@@ -477,7 +459,7 @@ describe('Class diagram', () => {
namespace testingNamespace {
class \`This\nTitle\nHas\nMany\nNewlines\` {
+String Also
- -Stirng Many
+ -String Many
#int Members
+And()
-Many()
diff --git a/cypress/integration/rendering/erDiagram-unified.spec.js b/cypress/integration/rendering/erDiagram-unified.spec.js
new file mode 100644
index 000000000..8cecba21d
--- /dev/null
+++ b/cypress/integration/rendering/erDiagram-unified.spec.js
@@ -0,0 +1,652 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+
+const testOptions = [
+ { description: '', options: { logLevel: 1 } },
+ { description: 'ELK: ', options: { logLevel: 1, layout: 'elk' } },
+ { description: 'HD: ', options: { logLevel: 1, look: 'handDrawn' } },
+];
+
+describe('Entity Relationship Diagram Unified', () => {
+ testOptions.forEach(({ description, options }) => {
+ it(`${description}should render a simple ER diagram`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a simple ER diagram without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render an ER diagram with a recursive relationship`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER ||..o{ CUSTOMER : refers
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with multiple relationships between the same two entities`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER ||--|{ ADDRESS : "invoiced at"
+ CUSTOMER ||--|{ ADDRESS : "receives goods at"
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a cyclical ER diagram`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ A ||--|{ B : likes
+ B ||--|{ C : likes
+ C ||--|{ A : likes
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a not-so-simple ER diagram`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER }|..|{ DELIVERY-ADDRESS : has
+ CUSTOMER ||--o{ ORDER : places
+ CUSTOMER ||--o{ INVOICE : "liable for"
+ DELIVERY-ADDRESS ||--o{ ORDER : receives
+ INVOICE ||--|{ ORDER : covers
+ ORDER ||--|{ ORDER-ITEM : includes
+ PRODUCT-CATEGORY ||--|{ PRODUCT : contains
+ PRODUCT ||--o{ ORDER-ITEM : "ordered in"
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a not-so-simple ER diagram without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ CUSTOMER }|..|{ DELIVERY-ADDRESS : has
+ CUSTOMER ||--o{ ORDER : places
+ CUSTOMER ||--o{ INVOICE : "liable for"
+ DELIVERY-ADDRESS ||--o{ ORDER : receives
+ INVOICE ||--|{ ORDER : covers
+ ORDER ||--|{ ORDER-ITEM : includes
+ PRODUCT-CATEGORY ||--|{ PRODUCT : contains
+ PRODUCT ||--o{ ORDER-ITEM : "ordered in"
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render multiple ER diagrams`, () => {
+ imgSnapshotTest(
+ [
+ `
+ erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ `
+ erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ ],
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with blank or empty labels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ BOOK }|..|{ AUTHOR : ""
+ BOOK }|..|{ GENRE : " "
+ AUTHOR }|..|{ GENRE : " "
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities that have no relationships`, () => {
+ renderGraph(
+ `
+ erDiagram
+ DEAD_PARROT
+ HERMIT
+ RECLUSE
+ SOCIALITE }o--o{ SOCIALITE : "interacts with"
+ RECLUSE }o--o{ SOCIALITE : avoids
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with and without attributes`, () => {
+ renderGraph(
+ `
+ erDiagram
+ BOOK { string title }
+ AUTHOR }|..|{ BOOK : writes
+ BOOK { float price }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with generic and array attributes`, () => {
+ renderGraph(
+ `
+ erDiagram
+ BOOK {
+ string title
+ string[] authors
+ type~T~ type
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with generic and array attributes without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ BOOK {
+ string title
+ string[] authors
+ type~T~ type
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with length in attributes type`, () => {
+ renderGraph(
+ `
+ erDiagram
+ CLUSTER {
+ varchar(99) name
+ string(255) description
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with length in attributes type without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ CLUSTER {
+ varchar(99) name
+ string(255) description
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities and attributes with big and small entity names`, () => {
+ renderGraph(
+ `
+ erDiagram
+ PRIVATE_FINANCIAL_INSTITUTION {
+ string name
+ int turnover
+ }
+ PRIVATE_FINANCIAL_INSTITUTION ||..|{ EMPLOYEE : employs
+ EMPLOYEE { bool officer_of_firm }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities and attributes with big and small entity names without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ PRIVATE_FINANCIAL_INSTITUTION {
+ string name
+ int turnover
+ }
+ PRIVATE_FINANCIAL_INSTITUTION ||..|{ EMPLOYEE : employs
+ EMPLOYEE { bool officer_of_firm }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with attributes that begin with asterisk`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ BOOK {
+ int *id
+ string name
+ varchar(99) summary
+ }
+ BOOK }o..o{ STORE : soldBy
+ STORE {
+ int *id
+ string name
+ varchar(50) address
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with attributes that begin with asterisk without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ BOOK {
+ int *id
+ string name
+ varchar(99) summary
+ }
+ BOOK }o..o{ STORE : soldBy
+ STORE {
+ int *id
+ string name
+ varchar(50) address
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with keys`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name PK
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ float price
+ string author FK
+ string title PK
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with keys without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name PK
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ float price
+ string author FK
+ string title PK
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with comments`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name "comment"
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ string author
+ string title "author comment"
+ float price "price comment"
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with comments without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name "comment"
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ string author
+ string title "author comment"
+ float price "price comment"
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with keys and comments`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name PK "comment"
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ string description
+ float price "price comment"
+ string title PK "title comment"
+ string author FK
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with keys and comments without htmlLabels`, () => {
+ renderGraph(
+ `
+ erDiagram
+ AUTHOR_WITH_LONG_ENTITY_NAME {
+ string name PK "comment"
+ }
+ AUTHOR_WITH_LONG_ENTITY_NAME }|..|{ BOOK : writes
+ BOOK {
+ string description
+ float price "price comment"
+ string title PK "title comment"
+ string author FK
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with aliases`, () => {
+ renderGraph(
+ `
+ erDiagram
+ T1 one or zero to one or more T2 : test
+ T2 one or many optionally to zero or one T3 : test
+ T3 zero or more to zero or many T4 : test
+ T4 many(0) to many(1) T5 : test
+ T5 many optionally to one T6 : test
+ T6 only one optionally to only one T1 : test
+ T4 0+ to 1+ T6 : test
+ T1 1 to 1 T3 : test
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a simple ER diagram with a title`, () => {
+ imgSnapshotTest(
+ `---
+ title: simple ER diagram
+ ---
+ erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ ORDER ||--|{ LINE-ITEM : contains
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with entity name aliases`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ p[Person] {
+ varchar(64) firstName
+ varchar(64) lastName
+ }
+ c["Customer Account"] {
+ varchar(128) email
+ }
+ p ||--o| c : has
+ `,
+ options
+ );
+ });
+
+ it(`${description}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
one"
+ p ||--o| b : "has
one
two"
+ p ||--o| c : "has
one
two
three"
+ p ||--o| d : "has
one
two
three
...
Nth"
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with unicode text`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ _**testáēŊÎļâÃđããŧ**_ {
+ *__List~List~int~~sdfds__* **driversLicense** PK "***The lđicense #***"
+ *string(99)~T~~~~~~* firstName "Only __99__
characters are a
llowed dsfsdfsdfsdfs"
+ string last*Name*
+ string __phone__ UK
+ int _age_
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with unicode text without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ _**testáēŊÎļâÃđããŧ**_ {
+ *__List~List~int~~sdfds__* **driversLicense** PK "***The lđicense #***"
+ *string(99)~T~~~~~~* firstName "Only __99__
characters are a
llowed dsfsdfsdfsdfs"
+ string last*Name*
+ string __phone__ UK
+ int _age_
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render an ER diagram with relationships with unicode text`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ person[đ] {
+ string *first*Name
+ string _**last**Name_
+ }
+ a["*Customer Account*"] {
+ **string** ema*i*l
+ }
+ person ||--o| a : __háēŊđ__
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with relationships with unicode text without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ person[đ] {
+ string *first*Name
+ string _**last**Name_
+ }
+ a["*Customer Account*"] {
+ **string** ema*i*l
+ }
+ person ||--o| a : __háēŊđ__
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render an ER diagram with TB direction`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ direction TB
+ CAR ||--|{ NAMED-DRIVER : allows
+ PERSON ||..o{ NAMED-DRIVER : is
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with BT direction`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ direction BT
+ CAR ||--|{ NAMED-DRIVER : allows
+ PERSON ||..o{ NAMED-DRIVER : is
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with LR direction`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ direction LR
+ CAR ||--|{ NAMED-DRIVER : allows
+ PERSON ||..o{ NAMED-DRIVER : is
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render an ER diagram with RL direction`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ direction RL
+ CAR ||--|{ NAMED-DRIVER : allows
+ PERSON ||..o{ NAMED-DRIVER : is
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with styles applied from style statement`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ c[CUSTOMER]
+ p[PERSON]
+ style c,p fill:#f9f,stroke:blue, color:grey, font-size:24px,font-weight:bold
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with styles applied from style statement without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ c[CUSTOMER]
+ p[PERSON]
+ style c,p fill:#f9f,stroke:blue, color:grey, font-size:24px,font-weight:bold
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with styles applied from class statement`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ c[CUSTOMER]
+ p[PERSON]:::blue
+ classDef bold font-size:24px, font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ class c,p bold
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render entities with styles applied from class statement without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ c[CUSTOMER]
+ p[PERSON]:::blue
+ classDef bold font-size:24px, font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ class c,p bold
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render entities with styles applied from the default class and other styles`, () => {
+ imgSnapshotTest(
+ `
+ erDiagram
+ c[CUSTOMER]
+ p[PERSON]:::blue
+ classDef blue stroke:lightblue, color: #0000FF
+ classDef default fill:pink
+ style c color:green
+ `,
+ { ...options }
+ );
+ });
+ });
+});
diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js
index 578f5a398..cbfec8218 100644
--- a/cypress/integration/rendering/erDiagram.spec.js
+++ b/cypress/integration/rendering/erDiagram.spec.js
@@ -109,8 +109,8 @@ describe('Entity Relationship Diagram', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- // use within because the absolute value can be slightly different depending on the environment Âą5%
- expect(maxWidthValue).to.be.within(140 * 0.95, 140 * 1.05);
+ // use within because the absolute value can be slightly different depending on the environment Âą6%
+ expect(maxWidthValue).to.be.within(140 * 0.96, 140 * 1.06);
});
});
@@ -125,8 +125,8 @@ describe('Entity Relationship Diagram', () => {
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
- // use within because the absolute value can be slightly different depending on the environment Âą5%
- expect(width).to.be.within(140 * 0.95, 140 * 1.05);
+ // use within because the absolute value can be slightly different depending on the environment Âą6%
+ expect(width).to.be.within(140 * 0.96, 140 * 1.06);
// expect(svg).to.have.attr('height', '465');
expect(svg).to.not.have.attr('style');
});
@@ -218,7 +218,6 @@ describe('Entity Relationship Diagram', () => {
`,
{ loglevel: 1 }
);
- cy.get('svg');
});
it('should render entities with keys', () => {
@@ -322,4 +321,37 @@ ORDER ||--|{ LINE-ITEM : contains
{ 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
one"
+ p ||--o| b : "has
one
two"
+ p ||--o| c : "has
one
two
three"
+ p ||--o| d : "has
one
two
three
...
Nth"
+ `,
+ { logLevel: 1 }
+ );
+ });
});
diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js
index e837565d3..9eeb2e310 100644
--- a/cypress/integration/rendering/errorDiagram.spec.js
+++ b/cypress/integration/rendering/errorDiagram.spec.js
@@ -3,7 +3,7 @@ import { imgSnapshotTest } from '../../helpers/util';
describe('Error Diagrams', () => {
beforeEach(() => {
cy.on('uncaught:exception', (err) => {
- expect(err.message).to.include('Parse error');
+ expect(err.message).to.include('error');
// return false to prevent the error from
// failing this test
return false;
diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js
index e931025e9..27af2c40c 100644
--- a/cypress/integration/rendering/flowchart-elk.spec.js
+++ b/cypress/integration/rendering/flowchart-elk.spec.js
@@ -1,6 +1,6 @@
-import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+import { imgSnapshotTest, renderGraph, verifyNumber } from '../../helpers/util.ts';
-describe.skip('Flowchart ELK', () => {
+describe('Flowchart ELK', () => {
it('1-elk: should render a simple flowchart', () => {
imgSnapshotTest(
`flowchart-elk TD
@@ -109,7 +109,7 @@ describe.skip('Flowchart ELK', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- expect(maxWidthValue).to.be.within(230 * 0.95, 230 * 1.05);
+ verifyNumber(maxWidthValue, 380);
});
});
it('8-elk: should render a flowchart when useMaxWidth is false', () => {
@@ -128,7 +128,7 @@ describe.skip('Flowchart ELK', () => {
const width = parseFloat(svg.attr('width'));
// 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(width).to.be.within(230 * 0.95, 230 * 1.05);
+ verifyNumber(width, 380);
expect(svg).to.not.have.attr('style');
});
});
@@ -208,13 +208,13 @@ describe.skip('Flowchart ELK', () => {
`flowchart-elk TB
internet
nat
- routeur
+ router
lb1
lb2
compute1
compute2
subgraph project
- routeur
+ router
nat
subgraph subnet1
compute1
@@ -225,8 +225,8 @@ describe.skip('Flowchart ELK', () => {
lb2
end
end
- internet --> routeur
- routeur --> subnet1 & subnet2
+ internet --> router
+ router --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
@@ -443,7 +443,7 @@ flowchart-elk TD
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
- it('63-elk: title on subgraphs should be themable', () => {
+ it('63-elk: title on subgraphs should be themeable', () => {
imgSnapshotTest(
`
%%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%%
@@ -692,7 +692,7 @@ A --> B
{}
);
cy.get('svg').should((svg) => {
- const edges = svg.querySelectorAll('.edges > path');
+ const edges = svg[0].querySelectorAll('.edges > path');
edges.forEach((edge) => {
expect(edge).to.have.class('flowchart-link');
});
@@ -739,7 +739,7 @@ NL\`") --"\`1o **bold**\`"--> c
{ flowchart: { titleTopMargin: 0 } }
);
});
- it('Wrapping long text with a new line', () => {
+ it.skip('Wrapping long text with a new line', () => {
imgSnapshotTest(
`%%{init: {"flowchart": {"htmlLabels": true}} }%%
flowchart-elk LR
@@ -837,6 +837,216 @@ subgraph "\`**Two**\`"
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+ it('Sub graphs', () => {
+ 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 } }
+ );
+ });
+
+ it('6088-1: should handle diamond shape intersections', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+ flowchart LR
+ subgraph S2
+ subgraph s1["APA"]
+ D{"Use the editor"}
+ end
+
+
+ D -- Mermaid js --> I{"fa:fa-code Text"}
+ D --> I
+ D --> I
+
+ end
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+
+ it('6088-2: should handle diamond shape intersections', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+ flowchart LR
+ a
+ subgraph s0["APA"]
+ subgraph s8["APA"]
+ subgraph s1["APA"]
+ D{"X"}
+ E[Q]
+ end
+ subgraph s3["BAPA"]
+ F[Q]
+ I
+ end
+ D --> I
+ D --> I
+ D --> I
+
+ I{"X"}
+ end
+ end
+
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+
+ it('6088-3: should handle diamond shape intersections', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+ flowchart LR
+ a
+ D{"Use the editor"}
+
+ D -- Mermaid js --> I{"fa:fa-code Text"}
+ D-->I
+ D-->I
+
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+
+ it('6088-4: 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 } }
+ );
+ });
+
+ it('6088-5: should handle diamond shape intersections', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+flowchart LR
+ A{A} --> B & C
+
+`,
+ { flowchart: { titleTopMargin: 0 } }
+ );
+ });
+ it('6088-6: should handle diamond shape intersections', () => {
+ imgSnapshotTest(
+ `---
+config:
+ layout: elk
+---
+flowchart LR
+ A{A} --> B & C
+ subgraph "subbe"
+ A
+ end
+
`,
{ flowchart: { titleTopMargin: 0 } }
);
@@ -855,7 +1065,7 @@ describe('Title and arrow styling #4813', () => {
flowchart LR
A-->B
A-->C`,
- { flowchart: { defaultRenderer: 'elk' } }
+ { layout: 'elk' }
);
cy.get('svg').should((svg) => {
const title = svg[0].querySelector('text');
@@ -871,15 +1081,14 @@ describe('Title and arrow styling #4813', () => {
B-.-oC
C==xD
D ~~~ A`,
- { flowchart: { defaultRenderer: 'elk' } }
+ { layout: 'elk' }
);
cy.get('svg').should((svg) => {
const edges = svg[0].querySelectorAll('.edges path');
- console.log(edges);
- expect(edges[0]).to.have.attr('pattern', 'solid');
- expect(edges[1]).to.have.attr('pattern', 'dotted');
- expect(edges[2]).to.have.css('stroke-width', '3.5px');
- expect(edges[3]).to.have.css('stroke-width', '1.5px');
+ 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');
});
});
});
diff --git a/cypress/integration/rendering/flowchart-handDrawn.spec.js b/cypress/integration/rendering/flowchart-handDrawn.spec.js
new file mode 100644
index 000000000..49c55c628
--- /dev/null
+++ b/cypress/integration/rendering/flowchart-handDrawn.spec.js
@@ -0,0 +1,1032 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+
+describe('Flowchart HandDrawn', () => {
+ it('FHD1: should render a simple flowchart no htmlLabels', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD2: should render a simple flowchart with htmlLabels', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: true },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD3: should render a simple flowchart with line breaks', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me thinksssss
ssssssssssssssssssssss
sssssssssssssssssssssssssss}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[Car]
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD4: should render a simple flowchart with trapezoid and inverse trapezoid vertex options.', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[/Christmas\\]
+ A -->|Get money| B[\\Go shopping/]
+ B --> C{Let me thinksssss
ssssssssssssssssssssss
sssssssssssssssssssssssssss}
+ C -->|One| D[/Laptop/]
+ C -->|Two| E[\\iPhone\\]
+ C -->|Three| F[Car]
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD5: should style nodes via a class.', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ 1A --> 1B
+ 1B --> 1C
+ 1C --> D
+ 1C --> E
+
+ classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f
+ class 1A,1B,D,E processHead
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD6: should render a flowchart full of circles', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ 47(SAM.CommonFA.FMESummary)-->48(SAM.CommonFA.CommonFAFinanceBudget)
+ 37(SAM.CommonFA.BudgetSubserviceLineVolume)-->48(SAM.CommonFA.CommonFAFinanceBudget)
+ 35(SAM.CommonFA.PopulationFME)-->47(SAM.CommonFA.FMESummary)
+ 41(SAM.CommonFA.MetricCost)-->47(SAM.CommonFA.FMESummary)
+ 44(SAM.CommonFA.MetricOutliers)-->47(SAM.CommonFA.FMESummary)
+ 46(SAM.CommonFA.MetricOpportunity)-->47(SAM.CommonFA.FMESummary)
+ 40(SAM.CommonFA.OPVisits)-->47(SAM.CommonFA.FMESummary)
+ 38(SAM.CommonFA.CommonFAFinanceRefund)-->47(SAM.CommonFA.FMESummary)
+ 43(SAM.CommonFA.CommonFAFinancePicuDays)-->47(SAM.CommonFA.FMESummary)
+ 42(SAM.CommonFA.CommonFAFinanceNurseryDays)-->47(SAM.CommonFA.FMESummary)
+ 45(SAM.CommonFA.MetricPreOpportunity)-->46(SAM.CommonFA.MetricOpportunity)
+ 35(SAM.CommonFA.PopulationFME)-->45(SAM.CommonFA.MetricPreOpportunity)
+ 41(SAM.CommonFA.MetricCost)-->45(SAM.CommonFA.MetricPreOpportunity)
+ 41(SAM.CommonFA.MetricCost)-->44(SAM.CommonFA.MetricOutliers)
+ 39(SAM.CommonFA.ChargeDetails)-->43(SAM.CommonFA.CommonFAFinancePicuDays)
+ 39(SAM.CommonFA.ChargeDetails)-->42(SAM.CommonFA.CommonFAFinanceNurseryDays)
+ 39(SAM.CommonFA.ChargeDetails)-->41(SAM.CommonFA.MetricCost)
+ 39(SAM.CommonFA.ChargeDetails)-->40(SAM.CommonFA.OPVisits)
+ 35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
+ 36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD7: should render a flowchart full of icons', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ 9e122290_1ec3_e711_8c5a_005056ad0002("fa:fa-creative-commons My System | Test Environment")
+ 82072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 1")
+ db052290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Business Logic Server:Service 2")
+ 4e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 1")
+ 30122290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Shared Report Server:Service 2")
+ 5e112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 1")
+ c1112290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs Dedicated Test Business Logic Server:Service 2")
+ b7042290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SupportDb]")
+ 8f102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[DevelopmentDb]")
+ 0e102290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[TestDb]")
+ 07132290_1ec3_e711_8c5a_005056ad0002("fa:fa-circle [DBServer\\SharedDbInstance].[SharedReportingDb]")
+ c7072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Business Logic Server")
+ ca122290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Shared Report Server")
+ 68102290_1ec3_e711_8c5a_005056ad0002("fa:fa-server Dedicated Test Business Logic Server")
+ f4112290_1ec3_e711_8c5a_005056ad0002("fa:fa-database [DBServer\\SharedDbInstance]")
+ d6072290_1ec3_e711_8c5a_005056ad0002("fa:fa-server DBServer")
+ 71082290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:MSSQLSERVER")
+ c0102290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLAgent")
+ 9a072290_1ec3_e711_8c5a_005056ad0002("fa:fa-cogs DBServer\\:SQLBrowser")
+ 1d0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost1")
+ 200a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost2")
+ 1c0a2290_1ec3_e711_8c5a_005056ad0002("fa:fa-server VmHost3")
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->db052290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->30122290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
+ 9e122290_1ec3_e711_8c5a_005056ad0002-->c1112290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
+ 82072290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
+ db052290_1ec3_e711_8c5a_005056ad0002-->c7072290_1ec3_e711_8c5a_005056ad0002
+ db052290_1ec3_e711_8c5a_005056ad0002-->82072290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->b7042290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->0e102290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->07132290_1ec3_e711_8c5a_005056ad0002
+ 4e112290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
+ 30122290_1ec3_e711_8c5a_005056ad0002-->ca122290_1ec3_e711_8c5a_005056ad0002
+ 30122290_1ec3_e711_8c5a_005056ad0002-->4e112290_1ec3_e711_8c5a_005056ad0002
+ 5e112290_1ec3_e711_8c5a_005056ad0002-->8f102290_1ec3_e711_8c5a_005056ad0002
+ 5e112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
+ c1112290_1ec3_e711_8c5a_005056ad0002-->68102290_1ec3_e711_8c5a_005056ad0002
+ c1112290_1ec3_e711_8c5a_005056ad0002-->5e112290_1ec3_e711_8c5a_005056ad0002
+ b7042290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 8f102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 0e102290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ 07132290_1ec3_e711_8c5a_005056ad0002-->f4112290_1ec3_e711_8c5a_005056ad0002
+ c7072290_1ec3_e711_8c5a_005056ad0002-->1d0a2290_1ec3_e711_8c5a_005056ad0002
+ ca122290_1ec3_e711_8c5a_005056ad0002-->200a2290_1ec3_e711_8c5a_005056ad0002
+ 68102290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->c0102290_1ec3_e711_8c5a_005056ad0002
+ f4112290_1ec3_e711_8c5a_005056ad0002-->9a072290_1ec3_e711_8c5a_005056ad0002
+ d6072290_1ec3_e711_8c5a_005056ad0002-->1c0a2290_1ec3_e711_8c5a_005056ad0002
+ 71082290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ c0102290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ c0102290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ 9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
+ 9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD8: should render labels with numbers at the start', () => {
+ imgSnapshotTest(
+ `
+ graph TB;subgraph "number as labels";1;end;
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD9: should render subgraphs', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph One
+ a1-->a2
+ end
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD10: should render subgraphs with a title starting with a digit', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph 2Two
+ a1-->a2
+ end
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD11: should render styled subgraphs', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ A
+ B
+ subgraph foo[Foo SubGraph]
+ C
+ D
+ end
+ subgraph bar[Bar SubGraph]
+ E
+ F
+ end
+ G
+
+ A-->B
+ B-->C
+ C-->D
+ B-->D
+ D-->E
+ E-->A
+ E-->F
+ F-->D
+ F-->G
+ B-->G
+ G-->D
+
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD12: should render a flowchart with long names and class definitions', () => {
+ imgSnapshotTest(
+ `graph LR
+ sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
+
+ æäē¤įŗč¯ˇ
+ į大
+ "];
+ class sid-B3655226-6C29-4D00-B685-3D5C734DC7E1 node-executed;
+ sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A["
+ č´č´ŖäēēåŽĄæš
+ åŧēå
+ "];
+ class sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A node-executed;
+ sid-E27C0367-E6D6-497F-9736-3CDC21FDE221["
+ DBAåŽĄæš
+ åŧēå
+ "];
+ class sid-E27C0367-E6D6-497F-9736-3CDC21FDE221 node-executed;
+ sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD["
+ SAåŽĄæš
+ éŋįž
+ "];
+ class sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD node-executed;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7["
+ ä¸ģįŽĄåŽĄæš
+ å
头åŧē
+ "];
+ class sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7 node-executed;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89["
+ DBAįĄŽčŽ¤
+ åŧēå
+ "];
+ class sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89 node-executed;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937["
+ SAįĄŽčŽ¤
+ éŋįž
+ "];
+ class sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937 node-executed;
+ sid-4FC27B48-A6F9-460A-A675-021F5854FE22["
+ įģæ
+ "];
+ class sid-4FC27B48-A6F9-460A-A675-021F5854FE22 node-executed;
+ sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E["
+ SAæ§čĄ1
+ åŧēå
+ "];
+ class sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E node-executed;
+ sid-6C2120F3-D940-4958-A067-0903DCE879C4["
+ SAæ§čĄ2
+ åŧēå
+ "];
+ class sid-6C2120F3-D940-4958-A067-0903DCE879C4 node-executed;
+ sid-9180E2A0-5C4B-435F-B42F-0D152470A338["
+ DBAæ§čĄ1
+ åŧēå
+ "];
+ class sid-9180E2A0-5C4B-435F-B42F-0D152470A338 node-executed;
+ sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD["
+ DBAæ§čĄ3
+ åŧēå
+ "];
+ class sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD node-executed;
+ sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756["
+ DBAæ§čĄ2
+ åŧēå
+ "];
+ class sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756 node-executed;
+ sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93["
+ DBAæ§čĄ4
+ åŧēå
+ "];
+ class sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93 node-executed;
+ sid-1897B30A-9C5C-4D5B-B80B-76A038785070["
+ č´č´ŖäēēįĄŽčŽ¤
+ æĸéčš
+ "];
+ class sid-1897B30A-9C5C-4D5B-B80B-76A038785070 node-executed;
+ sid-B3655226-6C29-4D00-B685-3D5C734DC7E1-->sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7;
+ sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A-->sid-1897B30A-9C5C-4D5B-B80B-76A038785070;
+ sid-E27C0367-E6D6-497F-9736-3CDC21FDE221-->sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89;
+ sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD-->sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937;
+ sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
+ sid-9180E2A0-5C4B-435F-B42F-0D152470A338-->sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756;
+ sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD-->sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93;
+ sid-6C2120F3-D940-4958-A067-0903DCE879C4-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-1897B30A-9C5C-4D5B-B80B-76A038785070-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-19DD9E9F-98C1-44EE-B604-842AFEE76F1E;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-9180E2A0-5C4B-435F-B42F-0D152470A338;
+ sid-A1B3CD96-7697-4D7C-BEAA-73D187B1BE89-->sid-03A2C3AC-5337-48A5-B154-BB3FD0EC8DAD;
+ sid-D5E1F2F4-306C-47A2-BF74-F66E3D769756-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-8C3F2F1D-F014-4F99-B966-095DC1A2BD93-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-BED98281-9585-4D1B-934E-BD1AC6AC0EFD;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-E27C0367-E6D6-497F-9736-3CDC21FDE221;
+ sid-3E35A7FF-A2F4-4E07-9247-DBF884C81937-->sid-6C2120F3-D940-4958-A067-0903DCE879C4;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
+ sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
+ `,
+ { look: 'handDrawn', fontFamily: 'courier' }
+ );
+ });
+
+ it('FHD13: should render color of styled nodes', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ foo-->bar
+
+ classDef foo fill:lightblue,color:green,stroke:#FF9E2C,font-weight:bold
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F
+ style bar fill:#999,color: #00ff00, stroke-width:10px,stroke:#0F0
+ `,
+ {
+ look: 'handDrawn',
+ listUrl: false,
+ listId: 'color styling',
+ fontFamily: 'courier',
+ logLevel: 0,
+ }
+ );
+ });
+
+ it('FHD14: should render hexagons', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{{Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?}}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[Car]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ listUrl: false,
+ listId: 'color styling',
+ fontFamily: 'courier',
+ logLevel: 0,
+ }
+ );
+ });
+
+ it('FHD15: should render a simple flowchart with comments', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD16: Render Stadium shape', () => {
+ imgSnapshotTest(
+ ` graph TD
+ A([stadium shape test])
+ A -->|Get money| B([Go shopping])
+ B --> C([Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?])
+ C -->|One| D([Laptop])
+ C -->|Two| E([iPhone])
+ C -->|Three| F([Car
wroom wroom])
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD17: Render multiline texts', () => {
+ imgSnapshotTest(
+ `graph LR
+ A1[Multi
Line] -->|Multi
Line| B1(Multi
Line)
+ C1[Multi
Line] -->|Multi
Line| D1(Multi
Line)
+ E1[Multi
Line] -->|Multi
Line| F1(Multi
Line)
+ A2[Multi
Line] -->|Multi
Line| B2(Multi
Line)
+ C2[Multi
Line] -->|Multi
Line| D2(Multi
Line)
+ E2[Multi
Line] -->|Multi
Line| F2(Multi
Line)
+ linkStyle 0 stroke:DarkGray,stroke-width:2px
+ linkStyle 1 stroke:DarkGray,stroke-width:2px
+ linkStyle 2 stroke:DarkGray,stroke-width:2px
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD18: Chaining of nodes', () => {
+ imgSnapshotTest(
+ `graph LR
+ a --> b --> c
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD19: Multiple nodes and chaining in one statement', () => {
+ imgSnapshotTest(
+ `graph LR
+ a --> b & c--> d
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FHD20: Multiple nodes and chaining in one statement', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
+ classDef exClass background:#bbb,border:1px solid red;
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH21: Render cylindrical shape', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[(cylindrical
shape
test)]
+ A -->|Get money| B1[(Go shopping 1)]
+ A -->|Get money| B2[(Go shopping 2)]
+ A -->|Get money| B3[(Go shopping 3)]
+ C[(Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?)]
+ B1 --> C
+ B2 --> C
+ B3 --> C
+ C -->|One| D[(Laptop)]
+ C -->|Two| E[(iPhone)]
+ C -->|Three| F[(Car)]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;`,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH22: Render a simple flowchart with nodeSpacing set to 100', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
+ );
+ });
+
+ it('FDH23: Render a simple flowchart with rankSpacing set to 100', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { rankSpacing: '100' },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH24: Keep node label text (if already defined) when a style is applied', () => {
+ imgSnapshotTest(
+ `graph LR
+ A(( )) -->|step 1| B(( ))
+ B(( )) -->|step 2| C(( ))
+ C(( )) -->|step 3| D(( ))
+ linkStyle 1 stroke:greenyellow,stroke-width:2px
+ style C fill:greenyellow,stroke:green,stroke-width:4px
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH25: Handle link click events (link, anchor, mailto, other protocol, script)', () => {
+ imgSnapshotTest(
+ `graph TB
+ TITLE["Link Click Events
(click the nodes below)"]
+ A["link test (open in same tab)"]
+ B["link test (open in new tab)"]
+ C[anchor test]
+ D[mailto test]
+ E[other protocol test]
+ F[script test]
+ TITLE --> A & B & C & D & E & F
+ click A "https://mermaid-js.github.io/mermaid/#/" "link test (open in same tab)"
+ click B "https://mermaid-js.github.io/mermaid/#/" "link test (open in new tab)" _blank
+ click C "#link-clicked"
+ click D "mailto:user@user.user" "mailto test"
+ click E "notes://do-your-thing/id" "other protocol test"
+ click F "javascript:alert('test')" "script test"
+ `,
+ { look: 'handDrawn', securityLevel: 'loose', fontFamily: 'courier' }
+ );
+ });
+
+ it('FDH26: Set text color of nodes and links according to styles when html labels are enabled', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[red
text] -->|red
text| B(blue
text)
+ C[/red
text/] -->|blue
text| D{blue
text}
+ E{{default
style}} -->|default
style| F([default
style])
+ linkStyle default color:Sienna;
+ linkStyle 0 color:red;
+ linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
+ style A color:red;
+ style B color:blue;
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ click B "index.html#link-clicked" "link test"
+ click D testClick "click test"
+ `,
+ { look: 'handDrawn', flowchart: { htmlLabels: true } }
+ );
+ });
+
+ it('FDH27: Set text color of nodes and links according to styles when html labels are disabled', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[red
text] -->|red
text| B(blue
text)
+ C[/red
text/] -->|blue
text| D{blue
text}
+ E{{default
style}} -->|default
style| F([default
style])
+ linkStyle default color:Sienna;
+ linkStyle 0 color:red;
+ linkStyle 1 stroke:DarkGray,stroke-width:2px,color:#0000ff
+ style A color:red;
+ style B color:blue;
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ click B "index.html#link-clicked" "link test"
+ click D testClick "click test"
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH28: Apply default class to all nodes which do not have another class assigned (htmlLabels enabled)', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[myClass1] --> B[default] & C[default]
+ B[default] & C[default] --> D[myClass2]
+ classDef default stroke-width:2px,fill:none,stroke:silver
+ classDef node color:red
+ classDef myClass1 color:#0000ff
+ classDef myClass2 stroke:#0000ff,fill:#ccccff
+ class A myClass1
+ class D myClass2
+ `,
+ { look: 'handDrawn', flowchart: { htmlLabels: true } }
+ );
+ });
+
+ it('FDH29: Apply default class to all nodes which do not have another class assigned (htmlLabels disabled)', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[myClass1] --> B[default] & C[default]
+ B[default] & C[default] --> D[myClass2]
+ classDef default stroke-width:2px,fill:none,stroke:silver
+ classDef node color:red
+ classDef myClass1 color:#0000ff
+ classDef myClass2 stroke:#0000ff,fill:#ccccff
+ class A myClass1
+ class D myClass2
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH30: Possibility to style text color of nodes and subgraphs as well as apply classes to subgraphs', () => {
+ imgSnapshotTest(
+ `graph LR
+ subgraph id1 [title is set]
+ A-->B
+ end
+ subgraph id2 [title]
+ E
+ end
+
+ B-->C
+
+ subgraph id3
+ C-->D
+ end
+ class id3,id2,A redBg;
+ class id3,A whiteTxt;
+ classDef redBg fill:#622;
+ classDef whiteTxt color: white;
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH31: should not slice off edges that are to the left of the left-most vertex', () => {
+ imgSnapshotTest(
+ `graph TD
+ work --> sleep
+ sleep --> work
+ eat --> sleep
+ work --> eat
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH32: Render Subroutine shape', () => {
+ imgSnapshotTest(
+ `graph LR
+ A[[subroutine shape test]]
+ A -->|Get money| B[[Go shopping]]
+ B --> C[[Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?]]
+ C -->|One| D[[Laptop]]
+ C -->|Two| E[[iPhone]]
+ C -->|Three| F[[Car
wroom wroom]]
+ click A "index.html#link-clicked" "link test"
+ click B testClick "click test"
+ classDef someclass fill:#f96;
+ class A someclass;
+ class C someclass;
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: false },
+ fontFamily: 'courier',
+ }
+ );
+ });
+
+ it('FDH33: should render a simple flowchart with diagramPadding set to 0', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ %% this is a comment
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', flowchart: { diagramPadding: 0 } }
+ );
+ });
+
+ it('FDH34: testing the label width in percy', () => {
+ imgSnapshotTest(
+ `graph TD
+ A[Christmas]
+ `,
+ { look: 'handDrawn', handDrawnSeed: 1 }
+ );
+ });
+
+ it('FDH35: should honor minimum edge length as specified by the user', () => {
+ imgSnapshotTest(
+ `graph TD
+ L1 --- L2
+ L2 --- C
+ M1 ---> C
+ R1 .-> R2
+ R2 <.-> C
+ C -->|Label 1| E1
+ C -- Label 2 ---> E2
+ C ----> E3
+ C -----> E4
+ C ======> E5
+ `,
+ { look: 'handDrawn', handDrawnSeed: 1 }
+ );
+ });
+ it('FDH36: should render escaped without html labels', () => {
+ imgSnapshotTest(
+ `graph TD
+ a["Haiya"]-->b
+ `,
+ { look: 'handDrawn', htmlLabels: false, flowchart: { htmlLabels: false } }
+ );
+ });
+ it('FDH37: should render non-escaped with html labels', () => {
+ imgSnapshotTest(
+ `graph TD
+ a["Haiya"]-->b
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH38: should render a flowchart when useMaxWidth is true (default)', () => {
+ renderGraph(
+ `flowchart TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', flowchart: { useMaxWidth: true } }
+ );
+ cy.get('svg').should((svg) => {
+ expect(svg).to.have.attr('width', '100%');
+ // expect(svg).to.have.attr('height');
+ // use within because the absolute value can be slightly different depending on the environment Âą10%
+ // const height = parseFloat(svg.attr('height'));
+ // expect(height).to.be.within(446 * 0.95, 446 * 1.05);
+ const style = svg.attr('style');
+ expect(style).to.match(/^max-width: [\d.]+px;$/);
+ const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
+ expect(maxWidthValue).to.be.within(446 * 0.9, 446 * 1.1);
+ });
+ });
+ it('FDH39: should render a flowchart when useMaxWidth is false', () => {
+ renderGraph(
+ `graph TD
+ A[Christmas] -->|Get money| B(Go shopping)
+ B --> C{Let me think}
+ C -->|One| D[Laptop]
+ C -->|Two| E[iPhone]
+ C -->|Three| F[fa:fa-car Car]
+ `,
+ { look: 'handDrawn', flowchart: { useMaxWidth: false } }
+ );
+ cy.get('svg').should((svg) => {
+ // const height = parseFloat(svg.attr('height'));
+ const width = parseFloat(svg.attr('width'));
+ // 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(width).to.be.within(446 * 0.9, 446 * 1.1);
+ expect(svg).to.not.have.attr('style');
+ });
+ });
+ it('FDH40: handle styling with style expressions', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ id1(Start)-->id2(Stop)
+ style id1 fill:#f9f,stroke:#333,stroke-width:4px
+ style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH41: handle styling for all node shapes', () => {
+ imgSnapshotTest(
+ `
+ graph LR
+ A[red text] -->|default style| B(blue text)
+ C([red text]) -->|default style| D[[blue text]]
+ E[(red text)] -->|default style| F((blue text))
+ G>red text] -->|default style| H{blue text}
+ I{{red text}} -->|default style| J[/blue text/]
+ linkStyle default color:Sienna;
+ style A stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style B stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style C stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style D stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style E stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style F stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style G stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style H stroke:#0000ff,fill:#ccccff,color:#0000ff
+ style I stroke:#ff0000,fill:#ffcccc,color:#ff0000
+ style J stroke:#0000ff,fill:#ccccff,color:#0000ff
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH42: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+graph TD
+ C -->|fa:fa-car Car| F[fa:fa-car Car]
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH43: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ subgraph bar[Bar]
+ F
+ end
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH44: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ graph TB
+ A
+ B
+ subgraph foo[Foo SubGraph]
+ C
+ D
+ end
+ subgraph bar[Bar SubGraph]
+ E
+ F
+ end
+ G
+
+ A-->B
+ B-->C
+ C-->D
+ B-->D
+ D-->E
+ E-->A
+ E-->F
+ F-->D
+ F-->G
+ B-->G
+ G-->D
+
+ style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
+ style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH45: fontawesome icons in edge labels', () => {
+ imgSnapshotTest(
+ `
+ %%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%%
+ flowchart LR
+ subgraph A
+ a --> b
+ end
+ subgraph B
+ i -->f
+ end
+ A --> B
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH46: text-color from classes', () => {
+ imgSnapshotTest(
+ `
+ flowchart LR
+ classDef dark fill:#000,stroke:#000,stroke-width:4px,color:#fff
+ Lorem --> Ipsum --> Dolor
+ class Lorem,Dolor dark
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('FDH47: apply class called default on node called default', () => {
+ imgSnapshotTest(
+ `
+ graph TD
+ classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
+ hello --> default
+ `,
+ {
+ look: 'handDrawn',
+ htmlLabels: true,
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+
+ it('FDH48: should be able to style default node independently', () => {
+ imgSnapshotTest(
+ `
+ flowchart TD
+ classDef default fill:#a34
+ hello --> default
+
+ style default stroke:#000,stroke-width:4px
+ `,
+ {
+ look: 'handDrawn',
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/flowchart-shape-alias.spec.ts b/cypress/integration/rendering/flowchart-shape-alias.spec.ts
new file mode 100644
index 000000000..86aef718c
--- /dev/null
+++ b/cypress/integration/rendering/flowchart-shape-alias.spec.ts
@@ -0,0 +1,142 @@
+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);
+ });
+ });
+});
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index 3eb2a0432..2e30d7a0c 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- expect(maxWidthValue).to.be.within(290 * 0.95 - 1, 290 * 1.05);
+ expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05);
});
});
it('8: should render a flowchart when useMaxWidth is false', () => {
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
const width = parseFloat(svg.attr('width'));
// 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(width).to.be.within(290 * 0.95 - 1, 290 * 1.05);
+ expect(width).to.be.within(417 * 0.95, 417 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
@@ -198,13 +198,13 @@ describe('Flowchart v2', () => {
`flowchart TB
internet
nat
- routeur
+ router
lb1
lb2
compute1
compute2
subgraph project
- routeur
+ router
nat
subgraph subnet1
compute1
@@ -215,8 +215,8 @@ describe('Flowchart v2', () => {
lb2
end
end
- internet --> routeur
- routeur --> subnet1 & subnet2
+ internet --> router
+ router --> subnet1 & subnet2
subnet1 & subnet2 --> nat --> internet
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
@@ -433,7 +433,7 @@ flowchart TD
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
- it('63: title on subgraphs should be themable', () => {
+ it('63: title on subgraphs should be themeable', () => {
imgSnapshotTest(
`
%%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%%
@@ -699,7 +699,7 @@ A --> B
{ flowchart: { titleTopMargin: 10 } }
);
});
- it('3192: It should be possieble to render flowcharts with invisible edges', () => {
+ it('3192: It should be possible to render flowcharts with invisible edges', () => {
imgSnapshotTest(
`---
title: Simple flowchart with invisible edges
@@ -786,7 +786,7 @@ A ~~~ B
`---
title: Subgraph nodeSpacing and rankSpacing example
config:
- flowchart:
+ flowchart:
nodeSpacing: 250
rankSpacing: 250
---
@@ -1047,7 +1047,69 @@ end
A --lb3--> TOP --lb4--> B
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 } },
+ }
+ );
+ });
+ });
+ describe('New @ syntax for node metadata edge cases', () => {
+ it('should be possible to use @ syntax to add labels on multi nodes', () => {
+ imgSnapshotTest(
+ `flowchart TB
+ n2["label for n2"] & n4@{ label: "label for n4"} & n5@{ label: "label for n5"}
+ `,
+ {}
+ );
+ });
+ it('should be possible to use @ syntax to add labels with trail spaces and &', () => {
+ imgSnapshotTest(
+ `flowchart TB
+ n2["label for n2"] & n4@{ label: "label for n4"} & n5@{ label: "label for n5"}
+ `,
+ {}
+ );
+ });
+ it('should be possible to use @ syntax to add labels with trail spaces', () => {
+ imgSnapshotTest(
+ `flowchart TB
+ n2["label for n2"]
+ n4@{ label: "label for n4"}
+ n5@{ label: "label for n5"}
+ `,
+ {}
+ );
+ });
+ it('should be possible to use @ syntax to add labels with trail spaces and edge/link', () => {
+ imgSnapshotTest(
+ `flowchart TD
+ A["A"] --> B["for B"] & C@{ label: "for c"} & E@{label : "for E"}
+ D@{label: "for D"}
+ `,
+ {}
);
});
});
diff --git a/cypress/integration/rendering/flowchart.spec.js b/cypress/integration/rendering/flowchart.spec.js
index e4766e792..7b986cd2f 100644
--- a/cypress/integration/rendering/flowchart.spec.js
+++ b/cypress/integration/rendering/flowchart.spec.js
@@ -733,7 +733,7 @@ describe('Graph', () => {
});
it('38: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph(
- `graph TD
+ `flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
@@ -751,7 +751,7 @@ describe('Graph', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
- expect(maxWidthValue).to.be.within(300 * 0.9, 300 * 1.1);
+ expect(maxWidthValue).to.be.within(446 * 0.9, 446 * 1.1);
});
});
it('39: should render a flowchart when useMaxWidth is false', () => {
@@ -770,7 +770,7 @@ describe('Graph', () => {
const width = parseFloat(svg.attr('width'));
// 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(width).to.be.within(300 * 0.9, 300 * 1.1);
+ expect(width).to.be.within(446 * 0.9, 446 * 1.1);
expect(svg).to.not.have.attr('style');
});
});
@@ -895,7 +895,7 @@ graph TD
imgSnapshotTest(
`
graph TD
- classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
+ classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
hello --> default
`,
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
@@ -905,13 +905,33 @@ graph TD
it('67: should be able to style default node independently', () => {
imgSnapshotTest(
`
- flowchart TD
+ flowchart TD
classDef default fill:#a34
hello --> default
style default stroke:#000,stroke-width:4px
`,
- { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
+ {
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
+ );
+ });
+ it('#6369: edge color should affect arrow head', () => {
+ imgSnapshotTest(
+ `
+ flowchart LR
+ A --> B
+ A --> C
+ C --> D
+
+ linkStyle 0 stroke:#D50000
+ linkStyle 2 stroke:#D50000
+ `,
+ {
+ flowchart: { htmlLabels: true },
+ securityLevel: 'loose',
+ }
);
});
});
diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js
index a0c2dbcb9..fc64f45d3 100644
--- a/cypress/integration/rendering/gantt.spec.js
+++ b/cypress/integration/rendering/gantt.spec.js
@@ -117,7 +117,7 @@ describe('Gantt diagram', () => {
{}
);
});
- it('should FAIL redering a gantt chart for issue #1060 with invalid date', () => {
+ it('should FAIL rendering a gantt chart for issue #1060 with invalid date', () => {
imgSnapshotTest(
`
gantt
@@ -573,7 +573,7 @@ describe('Gantt diagram', () => {
`
);
});
- it('should render a gantt diagram exculding friday and saturday', () => {
+ it('should render a gantt diagram excluding friday and saturday', () => {
imgSnapshotTest(
`gantt
title A Gantt Diagram
@@ -584,7 +584,7 @@ describe('Gantt diagram', () => {
A task :a1, 2024-02-28, 10d`
);
});
- it('should render a gantt diagram exculding saturday and sunday', () => {
+ it('should render a gantt diagram excluding saturday and sunday', () => {
imgSnapshotTest(
`gantt
title A Gantt Diagram
@@ -671,7 +671,7 @@ describe('Gantt diagram', () => {
title Gantt Digram
dateFormat YYYY-MM-DD
section Section
- ;A task with a semiclon :a1, 2014-01-01, 30d
+ ;A task with a semicolon :a1, 2014-01-01, 30d
Another task :after a1 , 20d
section Another
Task in sec :2014-01-12 , 12d
diff --git a/cypress/integration/rendering/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js
index 68b63de46..42dc57439 100644
--- a/cypress/integration/rendering/gitGraph.spec.js
+++ b/cypress/integration/rendering/gitGraph.spec.js
@@ -11,7 +11,7 @@ describe('Git Graph diagram', () => {
{}
);
});
- it('2: should render a simple gitgraph with commit on main branch with Id', () => {
+ it('2: should render a simple gitgraph with commit on main branch with id', () => {
imgSnapshotTest(
`gitGraph
commit id: "One"
@@ -253,7 +253,7 @@ describe('Git Graph diagram', () => {
`
gitGraph
checkout main
- %% Make sure to manually set the ID of all commits, for consistent visual tests
+ %% Make sure to manually set the id of all commits, for consistent visual tests
commit id: "1-abcdefg"
checkout main
branch branch1
@@ -343,7 +343,7 @@ gitGraph
{}
);
});
- it('16: should render a simple gitgraph with commit on main branch with Id | Vertical Branch', () => {
+ it('16: should render a simple gitgraph with commit on main branch with id | Vertical Branch', () => {
imgSnapshotTest(
`gitGraph TB:
commit id: "One"
@@ -585,7 +585,7 @@ gitGraph
`
gitGraph TB:
checkout main
- %% Make sure to manually set the ID of all commits, for consistent visual tests
+ %% Make sure to manually set the id of all commits, for consistent visual tests
commit id: "1-abcdefg"
checkout main
branch branch1
@@ -1024,7 +1024,7 @@ gitGraph TB:
{}
);
});
- it('51: should render a simple gitgraph with commit on main branch with Id | Vertical Branch - Bottom-to-top', () => {
+ it('51: should render a simple gitgraph with commit on main branch with id | Vertical Branch - Bottom-to-top', () => {
imgSnapshotTest(
`gitGraph BT:
commit id: "One"
@@ -1266,7 +1266,7 @@ gitGraph TB:
`
gitGraph BT:
checkout main
- %% Make sure to manually set the ID of all commits, for consistent visual tests
+ %% Make sure to manually set the id of all commits, for consistent visual tests
commit id: "1-abcdefg"
checkout main
branch branch1
@@ -1491,7 +1491,7 @@ gitGraph TB:
`
gitGraph
switch main
- %% Make sure to manually set the ID of all commits, for consistent visual tests
+ %% Make sure to manually set the id of all commits, for consistent visual tests
commit id: "1-abcdefg"
switch main
branch branch1
@@ -1532,5 +1532,41 @@ gitGraph TB:
{}
);
});
+ it('75: should render a gitGraph with multiple tags on a merge commit on bottom-to-top orientation', () => {
+ imgSnapshotTest(
+ `gitGraph BT:
+ commit id: "ZERO"
+ branch develop
+ commit id:"A"
+ checkout main
+ commit id:"ONE"
+ checkout develop
+ commit id:"B"
+ checkout main
+ merge develop id:"Release 1.0" type:HIGHLIGHT tag: "SAML v2.0" tag: "OpenID v1.1"
+ commit id:"TWO"
+ checkout develop
+ commit id:"C"`,
+ {}
+ );
+ });
+ });
+ it('76: should render a gitGraph with multiple tags on a merge commit on left-to-right orientation', () => {
+ imgSnapshotTest(
+ `gitGraph
+ commit id: "ZERO"
+ branch develop
+ commit id:"A"
+ checkout main
+ commit id:"ONE"
+ checkout develop
+ commit id:"B"
+ checkout main
+ merge develop id:"Release 1.0" type:HIGHLIGHT tag: "SAML v2.0" tag: "OpenID v1.1"
+ commit id:"TWO"
+ checkout develop
+ commit id:"C"`,
+ {}
+ );
});
});
diff --git a/cypress/integration/rendering/iconShape.spec.ts b/cypress/integration/rendering/iconShape.spec.ts
new file mode 100644
index 000000000..4c12c3fa3
--- /dev/null
+++ b/cypress/integration/rendering/iconShape.spec.ts
@@ -0,0 +1,143 @@
+import { imgSnapshotTest } from '../../helpers/util';
+
+const looks = ['classic', 'handDrawn'] as const;
+const directions = [
+ 'TB',
+ //'BT',
+ 'LR',
+ // 'RL'
+] as const;
+const forms = [undefined, 'square', 'circle', 'rounded'] as const;
+const labelPos = [undefined, 't', 'b'] as const;
+
+looks.forEach((look) => {
+ directions.forEach((direction) => {
+ forms.forEach((form) => {
+ labelPos.forEach((pos) => {
+ describe(`Test iconShape in ${form ? `${form} form,` : ''} ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
+ it(`without label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a label for icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with very long label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a very very very very very long long long label for icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:true`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** and strong for icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:false`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** and strong for icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, {
+ look,
+ htmlLabels: false,
+ flowchart: { htmlLabels: false },
+ });
+ });
+
+ it(`with styles`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with classDef`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
+ flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`;
+ if (form) {
+ flowchartCode += `, form: '${form}'`;
+ }
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ flowchartCode += ` nAA:::customClazz\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+ });
+ });
+ });
+ });
+});
+
+describe('Test iconShape with different h', () => {
+ it('with different h', () => {
+ let flowchartCode = `flowchart TB\n`;
+ const icon = 'fa:bell';
+ const iconHeight = 64;
+ flowchartCode += ` nA --> nAA@{ icon: '${icon}', label: 'icon with different h', h: ${iconHeight} }\n`;
+ imgSnapshotTest(flowchartCode);
+ });
+});
+
+describe('Test colored iconShape', () => {
+ it('with no styles', () => {
+ let flowchartCode = `flowchart TB\n`;
+ const icon = 'fluent-emoji:tropical-fish';
+ flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
+ imgSnapshotTest(flowchartCode);
+ });
+
+ it('with styles', () => {
+ let flowchartCode = `flowchart TB\n`;
+ const icon = 'fluent-emoji:tropical-fish';
+ flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
+ flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
+ imgSnapshotTest(flowchartCode);
+ });
+});
diff --git a/cypress/integration/rendering/imageShape.spec.ts b/cypress/integration/rendering/imageShape.spec.ts
new file mode 100644
index 000000000..d2e267149
--- /dev/null
+++ b/cypress/integration/rendering/imageShape.spec.ts
@@ -0,0 +1,103 @@
+import { imgSnapshotTest } from '../../helpers/util';
+
+const looks = ['classic', 'handDrawn'] as const;
+const directions = [
+ 'TB',
+ //'BT',
+ 'LR',
+ // 'RL'
+] as const;
+const labelPos = [undefined, 't', 'b'] as const;
+
+looks.forEach((look) => {
+ directions.forEach((direction) => {
+ labelPos.forEach((pos) => {
+ describe(`Test imageShape in ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => {
+ it(`without label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', w: '100', h: '100' }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a label for image shape'`;
+
+ flowchartCode += `, w: '100', h: '200'`;
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with very long label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a very very very very very long long long label for image shape'`;
+
+ flowchartCode += `, w: '100', h: '250'`;
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:true`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** and strong for image shape'`;
+
+ flowchartCode += `, w: '550', h: '200'`;
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, { look, htmlLabels: true });
+ });
+
+ it(`with markdown htmlLabels:false`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** and strong for image shape'`;
+ flowchartCode += `, w: '250', h: '200'`;
+
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ imgSnapshotTest(flowchartCode, {
+ look,
+ htmlLabels: false,
+ flowchart: { htmlLabels: false },
+ });
+ });
+
+ it(`with styles`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
+ flowchartCode += `, w: '550', h: '200'`;
+
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ flowchartCode += ` style A fill:#f9f,stroke:#333,stroke-width:4px \n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with classDef`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#000000,stroke-dasharray: 5 5\n`;
+ flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`;
+
+ flowchartCode += `, w: '500', h: '550'`;
+ if (pos) {
+ flowchartCode += `, pos: '${pos}'`;
+ }
+ flowchartCode += ` }\n`;
+ flowchartCode += ` A:::customClazz\n`;
+ imgSnapshotTest(flowchartCode, { look });
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/integration/rendering/journey.spec.js b/cypress/integration/rendering/journey.spec.js
index d8bef6d1b..70d5574b8 100644
--- a/cypress/integration/rendering/journey.spec.js
+++ b/cypress/integration/rendering/journey.spec.js
@@ -63,4 +63,199 @@ section Checkout from website
{ journey: { useMaxWidth: false } }
);
});
+
+ it('should initialize with a left margin of 150px for user journeys', () => {
+ renderGraph(
+ `
+ ---
+ config:
+ journey:
+ maxLabelWidth: 320
+ ---
+ journey
+ title User Journey Example
+ section Onboarding
+ Sign Up: 5:
+ Browse Features: 3:
+ Use Core Functionality: 4:
+ section Engagement
+ Browse Features: 3
+ Use Core Functionality: 4
+ `,
+ { journey: { useMaxWidth: true } }
+ );
+
+ let diagramStartX;
+
+ cy.contains('foreignobject', 'Sign Up').then(($diagram) => {
+ diagramStartX = parseFloat($diagram.attr('x'));
+ expect(diagramStartX).to.be.closeTo(150, 2);
+ });
+ });
+
+ it('should maintain sufficient space between legend and diagram when legend labels are longer', () => {
+ renderGraph(
+ `journey
+ title Web hook life cycle
+ section Darkoob
+ Make preBuilt:5: Darkoob user
+ register slug : 5: Darkoob userf deliberately increasing the size of this label to check if distance between legend and diagram is maintained
+ Map slug to a Prebuilt Job:5: Darkoob user
+ section External Service
+ set Darkoob slug as hook for an Event : 5 : admin Exjjjnjjjj qwerty
+ listen to the events : 5 : External Service
+ call darkoob endpoint : 5 : External Service
+ section Darkoob
+ check for inputs : 5 : DarkoobAPI
+ run the prebuilt job : 5 : DarkoobAPI
+ `,
+ { journey: { useMaxWidth: true } }
+ );
+
+ let LabelEndX, diagramStartX;
+
+ // Get right edge of the legend
+ cy.contains('tspan', 'Darkoob userf').then((textBox) => {
+ const bbox = textBox[0].getBBox();
+ LabelEndX = bbox.x + bbox.width;
+ });
+
+ // Get left edge of the diagram
+ cy.contains('foreignobject', 'Make preBuilt').then((rect) => {
+ diagramStartX = parseFloat(rect.attr('x'));
+ });
+
+ // Assert right edge of the diagram is greater than or equal to the right edge of the label
+ cy.then(() => {
+ expect(diagramStartX).to.be.gte(LabelEndX);
+ });
+ });
+
+ it('should wrap a single long word with hyphenation', () => {
+ renderGraph(
+ `
+ ---
+ config:
+ journey:
+ maxLabelWidth: 100
+ ---
+ journey
+ title Long Word Test
+ section Test
+ VeryLongWord: 5: Supercalifragilisticexpialidocious
+ `,
+ { journey: { useMaxWidth: true } }
+ );
+
+ // Verify that the line ends with a hyphen, indicating proper hyphenation for words exceeding maxLabelWidth.
+ cy.get('tspan').then((tspans) => {
+ const hasHyphen = [...tspans].some((t) => t.textContent.trim().endsWith('-'));
+ return expect(hasHyphen).to.be.true;
+ });
+ });
+
+ it('should wrap text on whitespace without adding hyphens', () => {
+ renderGraph(
+ `
+ ---
+ config:
+ journey:
+ maxLabelWidth: 200
+ ---
+ journey
+ title Whitespace Test
+ section Test
+ TextWithSpaces: 5: Gustavo Fring is played by Giancarlo Esposito and is a character in Breaking Bad.
+ `,
+ { journey: { useMaxWidth: true } }
+ );
+
+ // Verify that none of the text spans end with a hyphen.
+ cy.get('tspan').each(($el) => {
+ const text = $el.text();
+ expect(text.trim()).not.to.match(/-$/);
+ });
+ });
+
+ it('should wrap long labels into multiple lines, keep them under max width, and maintain margins', () => {
+ renderGraph(
+ `
+ ---
+ config:
+ journey:
+ maxLabelWidth: 320
+ ---
+ journey
+ title User Journey Example
+ section Onboarding
+ Sign Up: 5: This is a long label that will be split into multiple lines to test the wrapping functionality
+ Browse Features: 3: This is another long label that will be split into multiple lines to test the wrapping functionality
+ Use Core Functionality: 4: This is yet another long label that will be split into multiple lines to test the wrapping functionality
+ section Engagement
+ Browse Features: 3
+ Use Core Functionality: 4
+ `,
+ { journey: { useMaxWidth: true } }
+ );
+
+ let diagramStartX, maxLineWidth;
+
+ // Get the diagram's left edge x-coordinate
+ cy.contains('foreignobject', 'Sign Up')
+ .then(($diagram) => {
+ diagramStartX = parseFloat($diagram.attr('x'));
+ })
+ .then(() => {
+ cy.get('text.legend').then(($lines) => {
+ // Check that there are multiple lines
+ expect($lines.length).to.be.equal(9);
+
+ // Check that all lines are under the maxLabelWidth
+ $lines.each((index, el) => {
+ const bbox = el.getBBox();
+ expect(bbox.width).to.be.lte(320);
+ maxLineWidth = Math.max(maxLineWidth || 0, bbox.width);
+ });
+
+ /** The expected margin between the diagram and the legend is 150px, as defined by
+ * conf.leftMargin in user-journey-config.js
+ */
+ expect(diagramStartX - maxLineWidth).to.be.closeTo(150, 2);
+ });
+ });
+ });
+
+ it('should correctly render the user journey diagram title with the specified styling', () => {
+ renderGraph(
+ `---
+config:
+ journey:
+ titleColor: "#2900A5"
+ titleFontFamily: "Times New Roman"
+ titleFontSize: "5rem"
+---
+
+journey
+ title User Journey Example
+ section Onboarding
+ Sign Up: 5: John, Shahir
+ Complete Profile: 4: John
+ section Engagement
+ Browse Features: 3: John
+ Use Core Functionality: 4: John
+ section Retention
+ Revisit Application: 5: John
+ Invite Friends: 3: John
+
+ size: 2rem
+ `
+ );
+
+ cy.get('text').contains('User Journey Example').as('title');
+ cy.get('@title').then(($title) => {
+ expect($title).to.have.attr('fill', '#2900A5');
+ expect($title).to.have.attr('font-family', 'Times New Roman');
+ expect($title).to.have.attr('font-size', '5rem');
+ });
+ });
});
diff --git a/cypress/integration/rendering/kanban.spec.ts b/cypress/integration/rendering/kanban.spec.ts
new file mode 100644
index 000000000..0ff564cc6
--- /dev/null
+++ b/cypress/integration/rendering/kanban.spec.ts
@@ -0,0 +1,136 @@
+import { imgSnapshotTest } from '../../helpers/util.ts';
+
+describe('Kanban diagram', () => {
+ it('1: should render a kanban with a single section', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ docs[Create Documentation]
+ docs[Create Blog about the new diagram]
+ `,
+ {}
+ );
+ });
+ it('2: should render a kanban with multiple sections', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ docs[Create Documentation]
+ id2
+ docs[Create Blog about the new diagram]
+ `,
+ {}
+ );
+ });
+ it('3: should render a kanban with a single wrapping node', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
+ `,
+ {}
+ );
+ });
+ it('4: should handle the height of a section with a wrapping node at the end', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ id2[One line]
+ id3[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
+ `,
+ {}
+ );
+ });
+ it('5: should handle the height of a section with a wrapping node at the top', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
+ id3[One line]
+ `,
+ {}
+ );
+ });
+ it('6: should handle the height of a section with a wrapping node in the middle', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ id2[One line]
+ id3[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
+ id4[One line]
+ `,
+ {}
+ );
+ });
+ it('6: should handle assignments', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ docs[Create Documentation]
+ id2[In progress]
+ docs[Create Blog about the new diagram]@{ assigned: 'knsv' }
+ `,
+ {}
+ );
+ });
+ it('7: should handle prioritization', () => {
+ imgSnapshotTest(
+ `kanban
+ id2[In progress]
+ vh[Very High]@{ priority: 'Very High' }
+ h[High]@{ priority: 'High' }
+ m[Default priority]
+ l[Low]@{ priority: 'Low' }
+ vl[Very Low]@{ priority: 'Very Low' }
+ `,
+ {}
+ );
+ });
+ it('7: should handle external tickets', () => {
+ imgSnapshotTest(
+ `kanban
+ id1[Todo]
+ docs[Create Documentation]
+ id2[In progress]
+ docs[Create Blog about the new diagram]@{ ticket: MC-2037 }
+ `,
+ {}
+ );
+ });
+ it('8: should handle assignments, prioritization and tickets ids in the same item', () => {
+ imgSnapshotTest(
+ `kanban
+ id2[In progress]
+ docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
+ `,
+ {}
+ );
+ });
+ it('10: Full example', () => {
+ imgSnapshotTest(
+ `---
+config:
+ kanban:
+ ticketBaseUrl: 'https://abc123.atlassian.net/browse/#TICKET#'
+---
+kanban
+ id1[Todo]
+ docs[Create Documentation]
+ docs[Create Blog about the new diagram]
+ id7[In progress]
+ id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
+ id8[Design grammar]@{ assigned: 'knsv' }
+ id9[Ready for deploy]
+ id10[Ready for test]
+ id11[Done]
+ id5[define getData]
+ id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
+ id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
+ id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
+ id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
+ id12[Can't reproduce]
+ `,
+ {}
+ );
+ });
+});
diff --git a/cypress/integration/rendering/mindmap.spec.ts b/cypress/integration/rendering/mindmap.spec.ts
index a77459f58..731f861ee 100644
--- a/cypress/integration/rendering/mindmap.spec.ts
+++ b/cypress/integration/rendering/mindmap.spec.ts
@@ -146,7 +146,7 @@ root
shouldHaveRoot
);
});
- it('text shouhld wrap with icon', () => {
+ it('text should wrap with icon', () => {
imgSnapshotTest(
`mindmap
root
diff --git a/cypress/integration/rendering/newShapes.spec.ts b/cypress/integration/rendering/newShapes.spec.ts
new file mode 100644
index 000000000..6c71a3846
--- /dev/null
+++ b/cypress/integration/rendering/newShapes.spec.ts
@@ -0,0 +1,146 @@
+import { imgSnapshotTest } from '../../helpers/util.ts';
+
+const looks = ['classic', 'handDrawn'] as const;
+const directions = [
+ 'TB',
+ //'BT',
+ 'LR',
+ //'RL'
+] as const;
+const newShapesSet1 = [
+ 'triangle',
+ 'sloped-rectangle',
+ 'horizontal-cylinder',
+ 'flipped-triangle',
+ 'hourglass',
+] as const;
+const newShapesSet2 = [
+ 'tagged-rectangle',
+ 'documents',
+ 'lightning-bolt',
+ 'filled-circle',
+ 'window-pane',
+] as const;
+
+const newShapesSet3 = [
+ 'curved-trapezoid',
+ 'bow-rect',
+ 'tagged-document',
+ 'divided-rectangle',
+ 'crossed-circle',
+] as const;
+
+const newShapesSet4 = [
+ 'document',
+ 'notched-pentagon',
+ 'lined-cylinder',
+ 'stacked-document',
+ 'half-rounded-rectangle',
+] as const;
+
+const newShapesSet5 = [
+ 'lined-document',
+ 'tagged-document',
+ 'brace-l',
+ 'comment',
+ 'braces',
+ 'brace-r',
+] as const;
+
+const newShapesSet6 = ['brace-r', 'braces'] as const;
+// Aggregate all shape sets into a single array
+const newShapesSets = [
+ newShapesSet1,
+ newShapesSet2,
+ newShapesSet3,
+ newShapesSet4,
+ newShapesSet5,
+ newShapesSet6,
+];
+
+looks.forEach((look) => {
+ directions.forEach((direction) => {
+ newShapesSets.forEach((newShapesSet) => {
+ describe(`Test ${newShapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
+ it(`without label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`connect all shapes with each other`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
+ });
+ for (let i = 0; i < newShapesSet.length; i++) {
+ for (let j = i + 1; j < newShapesSet.length; j++) {
+ flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
+ }
+ }
+ if (!(direction === 'TB' && look === 'handDrawn' && newShapesSet === newShapesSet1)) {
+ //skip this test, works in real. Need to look
+ imgSnapshotTest(flowchartCode, { look });
+ }
+ });
+
+ it(`with very long label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:true`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** and strong for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:false`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** and strong for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, {
+ look,
+ htmlLabels: false,
+ flowchart: { htmlLabels: false },
+ });
+ });
+
+ it(`with styles`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
+ flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with classDef`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
+ newShapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
+ flowchartCode += ` n${index}${index}:::customClazz\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/integration/rendering/oldShapes.spec.ts b/cypress/integration/rendering/oldShapes.spec.ts
new file mode 100644
index 000000000..628e70ea8
--- /dev/null
+++ b/cypress/integration/rendering/oldShapes.spec.ts
@@ -0,0 +1,107 @@
+import { imgSnapshotTest } from '../../helpers/util';
+
+const looks = ['classic', 'handDrawn'] as const;
+const directions = [
+ 'TB',
+ //'BT',
+ 'LR',
+ //'RL'
+] as const;
+
+const shapesSet1 = ['text', 'card', 'lin-rect', 'diamond', 'hexagon'] as const;
+
+// removing labelRect, need have alias for it
+const shapesSet2 = ['rounded', 'rect', 'start', 'stop'] as const;
+
+const shapesSet3 = ['fork', 'choice', 'note', 'stadium', 'odd'] as const;
+
+const shapesSet4 = ['subroutine', 'cylinder', 'circle', 'doublecircle', 'odd'] as const;
+
+const shapesSet5 = ['anchor', 'lean-r', 'lean-l', 'trap-t', 'trap-b'] as const;
+
+// Aggregate all shape sets into a single array
+const shapesSets = [shapesSet1, shapesSet2, shapesSet3, shapesSet4, shapesSet5] as const;
+
+looks.forEach((look) => {
+ directions.forEach((direction) => {
+ shapesSets.forEach((shapesSet) => {
+ describe(`Test ${shapesSet.join(', ')} in ${look} look and dir ${direction}`, () => {
+ it(`without label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`connect all shapes with each other`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`;
+ });
+ for (let i = 0; i < shapesSet.length; i++) {
+ for (let j = i + 1; j < shapesSet.length; j++) {
+ flowchartCode += ` n${i}${i} --> n${j}${j}\n`;
+ }
+ }
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with very long label`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:true`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** and strong for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with markdown htmlLabels:false`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold** and strong for ${newShape} shape' }\n`;
+ });
+ imgSnapshotTest(flowchartCode, {
+ look,
+ htmlLabels: false,
+ flowchart: { htmlLabels: false },
+ });
+ });
+
+ it(`with styles`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
+ flowchartCode += ` style n${index}${index} fill:#f9f,stroke:#333,stroke-width:4px \n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+
+ it(`with classDef`, () => {
+ let flowchartCode = `flowchart ${direction}\n`;
+ flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`;
+ shapesSet.forEach((newShape, index) => {
+ flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`;
+ flowchartCode += ` n${index}${index}:::customClazz\n`;
+ });
+ imgSnapshotTest(flowchartCode, { look });
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/integration/rendering/packet.spec.ts b/cypress/integration/rendering/packet.spec.ts
index 61555ea53..c64538875 100644
--- a/cypress/integration/rendering/packet.spec.ts
+++ b/cypress/integration/rendering/packet.spec.ts
@@ -10,6 +10,15 @@ describe('packet structure', () => {
);
});
+ it('should render a simple packet diagram without ranges', () => {
+ imgSnapshotTest(
+ `packet-beta
+ 0: "h"
+ 1: "i"
+`
+ );
+ });
+
it('should render a complex packet diagram', () => {
imgSnapshotTest(
`packet-beta
diff --git a/cypress/integration/rendering/pie.spec.ts b/cypress/integration/rendering/pie.spec.ts
index 4a1d774c0..171a83057 100644
--- a/cypress/integration/rendering/pie.spec.ts
+++ b/cypress/integration/rendering/pie.spec.ts
@@ -64,7 +64,7 @@ describe('pie chart', () => {
});
});
- it('should render a pie diagram when textPosition is setted', () => {
+ it('should render a pie diagram when textPosition is set', () => {
imgSnapshotTest(
`pie
"Dogs": 50
diff --git a/cypress/integration/rendering/quadrantChart.spec.js b/cypress/integration/rendering/quadrantChart.spec.js
index 83a1455c6..3a1e768aa 100644
--- a/cypress/integration/rendering/quadrantChart.spec.js
+++ b/cypress/integration/rendering/quadrantChart.spec.js
@@ -8,7 +8,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render a complete quadrant chart', () => {
imgSnapshotTest(
@@ -30,7 +29,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render without points', () => {
imgSnapshotTest(
@@ -46,9 +44,8 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
- it('should able to render y-axix on right side', () => {
+ it('should able to render y-axis on right side', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"yAxisPosition": "right"}}}%%
@@ -63,9 +60,8 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
- it('should able to render x-axix on bottom', () => {
+ it('should able to render x-axis on bottom', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom"}}}%%
@@ -80,9 +76,8 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
- it('should able to render x-axix on bottom and y-axis on right', () => {
+ it('should able to render x-axis on bottom and y-axis on right', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom", "yAxisPosition": "right"}}}%%
@@ -97,7 +92,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render without title', () => {
imgSnapshotTest(
@@ -112,7 +106,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should use all the config', () => {
imgSnapshotTest(
@@ -135,7 +128,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should use all the theme variable', () => {
imgSnapshotTest(
@@ -158,7 +150,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render x-axis labels in the center, if x-axis has two labels', () => {
imgSnapshotTest(
@@ -180,7 +171,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render y-axis labels in the center, if y-axis has two labels', () => {
imgSnapshotTest(
@@ -202,7 +192,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('should render both axes labels on the left and bottom, if both axes have only one label', () => {
imgSnapshotTest(
@@ -224,7 +213,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('it should render data points with styles', () => {
@@ -249,7 +237,6 @@ describe('Quadrant Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('it should render data points with styles + classes', () => {
diff --git a/cypress/integration/rendering/radar.spec.js b/cypress/integration/rendering/radar.spec.js
new file mode 100644
index 000000000..b0bc3f6e0
--- /dev/null
+++ b/cypress/integration/rendering/radar.spec.js
@@ -0,0 +1,79 @@
+import { imgSnapshotTest } from '../../helpers/util';
+
+describe('radar structure', () => {
+ it('should render a simple radar diagram', () => {
+ imgSnapshotTest(
+ `radar-beta
+ title Best Radar Ever
+ axis A, B, C
+ curve c1{1, 2, 3}
+ `
+ );
+ });
+
+ it('should render a radar diagram with multiple curves', () => {
+ imgSnapshotTest(
+ `radar-beta
+ title Best Radar Ever
+ axis A, B, C
+ curve c1{1, 2, 3}
+ curve c2{2, 3, 1}
+ `
+ );
+ });
+
+ it('should render a complex radar diagram', () => {
+ imgSnapshotTest(
+ `radar-beta
+ title My favorite ninjas
+ axis Agility, Speed, Strength
+ axis Stam["Stamina"] , Intel["Intelligence"]
+
+ curve Ninja1["Naruto Uzumaki"]{
+ Agility 2, Speed 2,
+ Strength 3, Stam 5,
+ Intel 0
+ }
+ curve Ninja2["Sasuke"]{2, 3, 4, 1, 5}
+ curve Ninja3 {3, 2, 1, 5, 4}
+
+ showLegend true
+ ticks 3
+ max 8
+ min 0
+ graticule polygon
+ `
+ );
+ cy.get('svg').should((svg) => {
+ expect(svg).to.have.length(1);
+ });
+ });
+
+ it('should render radar diagram with config override', () => {
+ imgSnapshotTest(
+ `radar-beta
+ title Best Radar Ever
+ axis A,B,C
+ curve mycurve{1,2,3}`,
+ { radar: { marginTop: 100, axisScaleFactor: 0.5 } }
+ );
+ });
+
+ it('should parse radar diagram with theme override', () => {
+ imgSnapshotTest(
+ `radar-beta
+ axis A,B,C
+ curve mycurve{1,2,3}`,
+ { theme: 'base', themeVariables: { fontSize: 80, cScale0: '#FF0000' } }
+ );
+ });
+
+ it('should handle radar diagram with radar style override', () => {
+ imgSnapshotTest(
+ `radar-beta
+ axis A,B,C
+ curve mycurve{1,2,3}`,
+ { theme: 'base', themeVariables: { radar: { axisColor: '#FF0000' } } }
+ );
+ });
+});
diff --git a/cypress/integration/rendering/requirement.spec.js b/cypress/integration/rendering/requirement.spec.js
index f33ae7a0c..343441848 100644
--- a/cypress/integration/rendering/requirement.spec.js
+++ b/cypress/integration/rendering/requirement.spec.js
@@ -44,6 +44,5 @@ describe('Requirement diagram', () => {
`,
{}
);
- cy.get('svg');
});
});
diff --git a/cypress/integration/rendering/requirementDiagram-unified.spec.js b/cypress/integration/rendering/requirementDiagram-unified.spec.js
new file mode 100644
index 000000000..48b1a0d61
--- /dev/null
+++ b/cypress/integration/rendering/requirementDiagram-unified.spec.js
@@ -0,0 +1,703 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+
+const testOptions = [
+ { description: '', options: { logLevel: 1 } },
+ { description: 'ELK: ', options: { logLevel: 1, layout: 'elk' } },
+ { description: 'HD: ', options: { logLevel: 1, look: 'handDrawn' } },
+];
+
+describe('Requirement Diagram Unified', () => {
+ testOptions.forEach(({ description, options }) => {
+ it(`${description}should render a simple Requirement diagram`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a simple Requirement diagram without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render a not-so-simple Requirement diagram`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ functionalRequirement test_req2 {
+ id: 1.1
+ text: the second test text.
+ risk: low
+ verifymethod: inspection
+ }
+
+ performanceRequirement test_req3 {
+ id: 1.2
+ text: the third test text.
+ risk: medium
+ verifymethod: demonstration
+ }
+
+ interfaceRequirement test_req4 {
+ id: 1.2.1
+ text: the fourth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ physicalRequirement test_req5 {
+ id: 1.2.2
+ text: the fifth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ designConstraint test_req6 {
+ id: 1.2.3
+ text: the sixth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ element test_entity2 {
+ type: word doc
+ docRef: reqs/test_entity
+ }
+
+ element test_entity3 {
+ type: "test suite"
+ docRef: github.com/all_the_tests
+ }
+
+
+ test_entity - satisfies -> test_req2
+ test_req - traces -> test_req2
+ test_req - contains -> test_req3
+ test_req3 - contains -> test_req4
+ test_req4 - derives -> test_req5
+ test_req5 - refines -> test_req6
+ test_entity3 - verifies -> test_req5
+ test_req <- copies - test_entity2
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a not-so-simple Requirement diagram without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ functionalRequirement test_req2 {
+ id: 1.1
+ text: the second test text.
+ risk: low
+ verifymethod: inspection
+ }
+
+ performanceRequirement test_req3 {
+ id: 1.2
+ text: the third test text.
+ risk: medium
+ verifymethod: demonstration
+ }
+
+ interfaceRequirement test_req4 {
+ id: 1.2.1
+ text: the fourth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ physicalRequirement test_req5 {
+ id: 1.2.2
+ text: the fifth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ designConstraint test_req6 {
+ id: 1.2.3
+ text: the sixth test text.
+ risk: medium
+ verifymethod: analysis
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ element test_entity2 {
+ type: word doc
+ docRef: reqs/test_entity
+ }
+
+ element test_entity3 {
+ type: "test suite"
+ docRef: github.com/all_the_tests
+ }
+
+
+ test_entity - satisfies -> test_req2
+ test_req - traces -> test_req2
+ test_req - contains -> test_req3
+ test_req3 - contains -> test_req4
+ test_req4 - derives -> test_req5
+ test_req5 - refines -> test_req6
+ test_entity3 - verifies -> test_req5
+ test_req <- copies - test_entity2
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render multiple Requirement diagrams`, () => {
+ imgSnapshotTest(
+ [
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ ],
+ options
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with empty information`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ requirement test_req {
+ }
+ element test_entity {
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with and without information`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+ element test_entity {
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with long and short text`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement test_req {
+ id: 1
+ text: the test text that is long and takes up a lot of space.
+ risk: high
+ verifymethod: test
+ }
+ element test_entity_name_that_is_extra_long {
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with long and short text without htmlLabels`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement test_req {
+ id: 1
+ text: the test text that is long and takes up a lot of space.
+ risk: high
+ verifymethod: test
+ }
+ element test_entity_name_that_is_extra_long {
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render requirements and elements with quoted text for spaces`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement "test req name with spaces" {
+ id: 1
+ text: the test text that is long and takes up a lot of space.
+ risk: high
+ verifymethod: test
+ }
+ element "test entity name that is extra long with spaces" {
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with markdown text`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement "__my bolded name__" {
+ id: 1
+ text: "**Bolded text** _italicized text_"
+ risk: high
+ verifymethod: test
+ }
+ element "*my italicized name*" {
+ type: "**Bolded type** _italicized type_"
+ docref: "*Italicized* __Bolded__"
+ }
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with markdown text without htmlLabels`, () => {
+ renderGraph(
+ `
+ requirementDiagram
+ requirement "__my bolded name__" {
+ id: 1
+ text: "**Bolded text** _italicized text_"
+ risk: high
+ verifymethod: test
+ }
+ element "*my italicized name*" {
+ type: "**Bolded type** _italicized type_"
+ docref: "*Italicized* __Bolded__"
+ }
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render a simple Requirement diagram with a title`, () => {
+ imgSnapshotTest(
+ `---
+ title: simple Requirement diagram
+ ---
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with TB direction`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ direction TB
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with BT direction`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ direction BT
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with LR direction`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ direction LR
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with RL direction`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+ direction RL
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from style statement`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+
+ style test_req,test_entity fill:#f9f,stroke:blue, color:grey, font-weight:bold
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from style statement without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+
+ style test_req,test_entity fill:#f9f,stroke:blue, color:grey, font-weight:bold
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from class statement`, () => {
+ imgSnapshotTest(
+ `
+requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef bold font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ class test_entity bold
+ class test_req blue, bold
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from class statement without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef bold font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ class test_entity bold
+ class test_req blue, bold
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from classes with shorthand syntax`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req:::blue {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef bold font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ test_entity:::bold
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from classes with shorthand syntax without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req:::blue {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef bold font-weight: bold
+ classDef blue stroke:lightblue, color: #0000FF
+ test_entity:::bold
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from the default class and other styles`, () => {
+ imgSnapshotTest(
+ `
+requirementDiagram
+
+ requirement test_req:::blue {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef blue stroke:lightblue, color:blue
+ classDef default fill:pink
+ style test_entity color:green
+ `,
+ options
+ );
+ });
+
+ it(`${description}should render requirements and elements with styles applied from the default class and other styles without htmlLabels`, () => {
+ imgSnapshotTest(
+ `
+ requirementDiagram
+
+ requirement test_req:::blue {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ classDef blue stroke:lightblue, color:blue
+ classDef default fill:pink
+ style test_entity color:green
+ `,
+ { ...options, htmlLabels: false }
+ );
+ });
+
+ it(`${description}should render a Requirement diagram with a theme`, () => {
+ imgSnapshotTest(
+ `
+---
+ theme: forest
+---
+ requirementDiagram
+
+ requirement test_req:::blue {
+ id: 1
+ text: the test text.
+ risk: high
+ verifymethod: test
+ }
+
+ element test_entity {
+ type: simulation
+ }
+
+ test_entity - satisfies -> test_req
+ `,
+ options
+ );
+ });
+ });
+});
diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js
index 1285a0832..f18e99abf 100644
--- a/cypress/integration/rendering/sequencediagram.spec.js
+++ b/cypress/integration/rendering/sequencediagram.spec.js
@@ -1,8 +1,6 @@
-///
The people who live here are hunter-gatherers.
- section Broze Age
+ section Bronze Age
2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking.
: New styles of pottery and ways of burying the dead appear.
2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles.
@@ -51,7 +51,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
`,
{}
@@ -68,7 +68,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
@@ -84,7 +84,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
@@ -101,7 +101,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
@@ -118,7 +118,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
@@ -135,7 +135,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
@@ -152,7 +152,7 @@ describe('Timeline diagram', () => {
title History of Social Media Platform
2002 : LinkedIn
2004 : Facebook : Google
- 2005 : Youtube
+ 2005 : YouTube
2006 : Twitter
2007 : Tumblr
2008 : Instagram
diff --git a/cypress/integration/rendering/xyChart.spec.js b/cypress/integration/rendering/xyChart.spec.js
index 85d998c50..a582355e8 100644
--- a/cypress/integration/rendering/xyChart.spec.js
+++ b/cypress/integration/rendering/xyChart.spec.js
@@ -9,7 +9,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Should render a complete chart', () => {
imgSnapshotTest(
@@ -35,7 +34,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('y-axis title not required', () => {
imgSnapshotTest(
@@ -48,7 +46,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Should render a chart without y-axis with different range', () => {
imgSnapshotTest(
@@ -60,7 +57,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('x axis title not required', () => {
imgSnapshotTest(
@@ -72,7 +68,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Multiple plots can be rendered', () => {
imgSnapshotTest(
@@ -87,7 +82,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Decimals and negative numbers are supported', () => {
imgSnapshotTest(
@@ -98,7 +92,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render spark line with "plotReservedSpacePercent"', () => {
imgSnapshotTest(
@@ -116,7 +109,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render spark bar without displaying other property', () => {
imgSnapshotTest(
@@ -143,7 +135,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Should use all the config from directive', () => {
imgSnapshotTest(
@@ -158,7 +149,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Should use all the config from yaml', () => {
imgSnapshotTest(
@@ -189,6 +179,7 @@ describe('XY Chart', () => {
axisLineWidth: 5
chartOrientation: horizontal
plotReservedSpacePercent: 60
+ showDataLabel: true
---
xychart-beta
title "Sales Revenue"
@@ -199,7 +190,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render with show axis title false', () => {
imgSnapshotTest(
@@ -221,7 +211,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render with show axis label false', () => {
imgSnapshotTest(
@@ -243,7 +232,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render with show axis tick false', () => {
imgSnapshotTest(
@@ -265,7 +253,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render with show axis line false', () => {
imgSnapshotTest(
@@ -287,7 +274,6 @@ describe('XY Chart', () => {
`,
{}
);
- cy.get('svg');
});
it('Render all the theme color', () => {
imgSnapshotTest(
@@ -317,6 +303,529 @@ describe('XY Chart', () => {
`,
{}
);
+ });
+ it('should use the correct distances between data points', () => {
+ imgSnapshotTest(
+ `
+ xychart-beta
+ x-axis 0 --> 2
+ line [0, 1, 0, 1]
+ bar [1, 0, 1, 0]
+ `,
+ {}
+ );
cy.get('svg');
});
+
+ it('should render vertical bar chart with labels', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
+ y-axis "Revenue (in $)" 4000 --> 11000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
+ `,
+ {}
+ );
+ });
+
+ it('should render horizontal bar chart with labels', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
+ y-axis "Revenue (in $)" 4000 --> 11000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
+ `,
+ {}
+ );
+ });
+
+ it('should render vertical bar chart without labels by default', () => {
+ imgSnapshotTest(
+ `
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
+ y-axis "Revenue (in $)" 4000 --> 11000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
+ `,
+ {}
+ );
+ });
+
+ it('should render horizontal bar chart without labels by default', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
+ y-axis "Revenue (in $)" 4000 --> 11000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
+ `,
+ {}
+ );
+ });
+
+ it('should render multiple bar plots vertically with labels correctly', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Multiple Bar Plots"
+ x-axis Categories [A, B, C]
+ y-axis "Values" 0 --> 100
+ bar [10, 50, 90]
+ `,
+ {}
+ );
+ });
+
+ it('should render multiple bar plots horizontally with labels correctly', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Multiple Bar Plots"
+ x-axis Categories [A, B, C]
+ y-axis "Values" 0 --> 100
+ bar [10, 50, 90]
+ `,
+ {}
+ );
+ });
+
+ it('should render a single bar with label for a vertical xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Single Bar Chart"
+ x-axis Categories [A]
+ y-axis "Value" 0 --> 100
+ bar [75]
+ `,
+ {}
+ );
+ });
+
+ it('should render a single bar with label for a horizontal xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Single Bar Chart"
+ x-axis Categories [A]
+ y-axis "Value" 0 --> 100
+ bar [75]
+ `,
+ {}
+ );
+ });
+
+ it('should render negative and decimal values with correct labels for vertical xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Decimal and Negative Values"
+ x-axis Categories [A, B, C]
+ y-axis -10 --> 10
+ bar [ -2.5, 0.75, 5.1 ]
+ `,
+ {}
+ );
+ });
+
+ it('should render negative and decimal values with correct labels for horizontal xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Decimal and Negative Values"
+ x-axis Categories [A, B, C]
+ y-axis -10 --> 10
+ bar [ -2.5, 0.75, 5.1 ]
+ `,
+ {}
+ );
+ });
+
+ it('should render data labels within each bar in the vertical xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan,b,c]
+ y-axis "Revenue (in $)" 4000 --> 12000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ // Check horizontal alignment (within tolerance)
+ expect(textProps.x + textProps.width / 2).to.be.closeTo(
+ barProps.x + barProps.width / 2,
+ 5
+ );
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ });
+ });
+ });
+ });
+
+ it('should render data labels within each bar in the horizontal xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan,b,c]
+ y-axis "Revenue (in $)" 4000 --> 12000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 3000, 2000, 500, 2000, 3000, 11000, 5000, 6000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ expect(textProps.y + textProps.height / 2).to.be.closeTo(
+ barProps.y + barProps.height / 2,
+ 5
+ );
+ });
+ });
+ });
+ });
+
+ it('should render data labels within each bar in the vertical xy-chart with a lot of bars of different sizes', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
+ y-axis "Revenue (in $)" 4000 --> 12000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ // Check horizontal alignment (within tolerance)
+ expect(textProps.x + textProps.width / 2).to.be.closeTo(
+ barProps.x + barProps.width / 2,
+ 5
+ );
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ });
+ });
+ });
+ });
+
+ it('should render data labels within each bar in the horizontal xy-chart with a lot of bars of different sizes', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s]
+ y-axis "Revenue (in $)" 4000 --> 12000
+ bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000, 8000, 10000, 5000, 7600, 4999,11000 ,5000,6000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ expect(textProps.y + textProps.height / 2).to.be.closeTo(
+ barProps.y + barProps.height / 2,
+ 5
+ );
+ });
+ });
+ });
+ });
+
+ it('should render data labels correctly for a bar in the vertical xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan]
+ y-axis "Revenue (in $)" 3000 --> 12000
+ bar [4000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ // Check horizontal alignment (within tolerance)
+ expect(textProps.x + textProps.width / 2).to.be.closeTo(
+ barProps.x + barProps.width / 2,
+ 5
+ );
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ });
+ });
+ });
+ });
+
+ it('should render data labels correctly for a bar in the horizontal xy-chart', () => {
+ imgSnapshotTest(
+ `
+ ---
+ config:
+ xyChart:
+ showDataLabel: true
+ chartOrientation: horizontal
+ ---
+ xychart-beta
+ title "Sales Revenue"
+ x-axis Months [jan]
+ y-axis "Revenue (in $)" 3000 --> 12000
+ bar [4000]
+ `,
+ {}
+ );
+
+ cy.get('g.bar-plot-0').within(() => {
+ cy.get('rect').each(($rect, index) => {
+ // Extract bar properties
+ const barProps = {
+ x: parseFloat($rect.attr('x')),
+ y: parseFloat($rect.attr('y')),
+ width: parseFloat($rect.attr('width')),
+ height: parseFloat($rect.attr('height')),
+ };
+
+ // Get the text element corresponding to this bar by index.
+ cy.get('text')
+ .eq(index)
+ .then(($text) => {
+ const bbox = $text[0].getBBox();
+ const textProps = {
+ x: bbox.x,
+ y: bbox.y,
+ width: bbox.width,
+ height: bbox.height,
+ };
+
+ // Verify that the text label is positioned within the boundaries of the bar.
+ expect(textProps.x).to.be.greaterThan(barProps.x);
+ expect(textProps.x + textProps.width).to.be.lessThan(barProps.x + barProps.width);
+
+ expect(textProps.y).to.be.greaterThan(barProps.y);
+ expect(textProps.y + textProps.height).to.be.lessThan(barProps.y + barProps.height);
+ expect(textProps.y + textProps.height / 2).to.be.closeTo(
+ barProps.y + barProps.height / 2,
+ 5
+ );
+ });
+ });
+ });
+ });
});
diff --git a/cypress/platform/architecture-external.html b/cypress/platform/architecture-external.html
new file mode 100644
index 000000000..71770425d
--- /dev/null
+++ b/cypress/platform/architecture-external.html
@@ -0,0 +1,52 @@
+
+
+
+ architecture-beta + service s3(logos:aws-s3)[Cloud Store] + service ec2(logos:aws-ec2)[Server] + service api(logos:aws-api-gateway)[Api Gateway] + service fa(fa:image)[Font Awesome Icon] ++ + + + diff --git a/cypress/platform/ashish2.html b/cypress/platform/ashish2.html index f9132d2e2..30584295d 100644 --- a/cypress/platform/ashish2.html +++ b/cypress/platform/ashish2.html @@ -4,7 +4,7 @@ timeline title My day - section section with no tasks + section Section with no tasks section Go to work at the dog office 1930 : first step : second step is a long step : third step @@ -70,18 +70,18 @@ 1960 : India fights poverty, looses war to China and gets nuclear weapons from USA and USSR 1970 : Green Revolution comes to india section Another section with no tasks - I am a big big big tasks - I am not so big tasks + I am a very, very big task + I am not so big task
timeline title MermaidChart 2023 Timeline section 2023 Q1
Release Personal Tier - Buttet 1 : sub-point 1a : sub-point 1b + Bullet 1 : sub-point 1a : sub-point 1b : sub-point 1c Bullet 2 : sub-point 2a : sub-point 2b section 2023 Q2
Release XYZ Tier - Buttet 3 : sub-point
3a : sub-point 3b + Bullet 3 : sub-point
3a : sub-point 3b : sub-point 3c Bullet 4 : sub-point 4a : sub-point 4b @@ -93,7 +93,7 @@ section Stone Age 7600 BC : Britain's oldest known house was built in Orkney, Scotland 6000 BC : Sea levels rise and Britain becomes an island. The people who live here are hunter-gatherers. - section Broze Age + section Bronze Age 2300 BC : People arrive from Europe and settle in Britain. They bring farming and metalworking. : New styles of pottery and ways of burying the dead appear. 2200 BC : The last major building works are completed at Stonehenge. People now bury their dead in stone circles. @@ -106,7 +106,7 @@ title History of Social Media Platform 2002 : LinkedIn 2004 : Facebook : Google : Pixar - 2005 : Youtube + 2005 : YouTube 2006 : Twitter 2007 : Tumblr 2008s : Instagram @@ -122,7 +122,7 @@ title History of Social Media Platform 2002 : LinkedIn 2004 : Facebook : Google : Pixar - 2005 : Youtube + 2005 : YouTube 2006 : Twitter 2007 : Tumblr 2008s : Instagram @@ -139,7 +139,7 @@ title History of Social Media Platform 2002 : LinkedIn 2004 : Facebook : Google - 2005 : Youtube + 2005 : YouTube 2006 : Twitter 2007 : Tumblr 2008 : Instagram @@ -152,7 +152,7 @@ title History of Social Media Platform 2002 : LinkedIn 2004 : Facebook : Google - 2005 : Youtube + 2005 : YouTube 2006 : Twitter 2007 : Tumblr 2008s : Instagram diff --git a/cypress/platform/bundle-test.js b/cypress/platform/bundle-test.js index f5bf0ecd6..24ce8d753 100644 --- a/cypress/platform/bundle-test.js +++ b/cypress/platform/bundle-test.js @@ -27,7 +27,7 @@ const code3 = `flowchart TD A() B(Bold text!)`; -if (location.href.match('test-html-escaping')) { +if (/test-html-escaping/.exec(location.href)) { code = code3; } diff --git a/cypress/platform/class.html b/cypress/platform/class.html index 0b1a5729a..b649d1184 100644 --- a/cypress/platform/class.html +++ b/cypress/platform/class.html @@ -3,7 +3,7 @@