From 83d7d6c48f829176282ba3f92d416e9dca20faf1 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 05:42:02 +0100 Subject: [PATCH 01/10] fix: fix invalid CSS for class diagram `.divider` had a `stroke` property of `1` that was invalid. This looks like a typo from PR https://github.com/mermaid-js/mermaid/pull/1567, as the `src/theme/class.scss` file's `.divider` section correctly shows `stroke-width: 1;`. Fixes: https://github.com/mermaid-js/mermaid/pull/1567 --- packages/mermaid/src/diagrams/class/styles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/class/styles.js b/packages/mermaid/src/diagrams/class/styles.js index 981cd7b73..15386bf9e 100644 --- a/packages/mermaid/src/diagrams/class/styles.js +++ b/packages/mermaid/src/diagrams/class/styles.js @@ -41,7 +41,7 @@ const getStyles = (options) => .divider { stroke: ${options.nodeBorder}; - stroke: 1; + stroke-width: 1; } g.clickable { From 616d370a513e6843c2a580c793248c83d8fccef7 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 05:48:02 +0100 Subject: [PATCH 02/10] fix: fix personBorder/Bkg C4 diagram theme vars The current `personBorder`/`personBkg` theme variables for C4 diagrams are set to the string `'calculated'`. However, despite being `'calculated'`, they never seem to change to anything else, and so become invalid CSS variables. I've instead changed these to just default to base theme vars, as that's what they do in [`these-base.js`][1]. [1]: https://github.com/mermaid-js/mermaid/blob/727bf30824e08c672c7f13ed83f111c2d1596935/packages/mermaid/src/themes/theme-base.js#L106-L107 --- packages/mermaid/src/themes/theme-dark.js | 5 ++--- packages/mermaid/src/themes/theme-default.js | 5 ++--- packages/mermaid/src/themes/theme-forest.js | 5 ++--- packages/mermaid/src/themes/theme-neutral.js | 5 ++--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/themes/theme-dark.js b/packages/mermaid/src/themes/theme-dark.js index 9585a2e27..6ee26169d 100644 --- a/packages/mermaid/src/themes/theme-dark.js +++ b/packages/mermaid/src/themes/theme-dark.js @@ -81,9 +81,8 @@ class Theme { this.todayLineColor = '#DB5757'; /* C4 Context Diagram variables */ - - this.personBorder = 'calculated'; - this.personBkg = 'calculated'; + this.personBorder = this.primaryBorderColor; + this.personBkg = this.mainBkg; /* state colors */ this.labelColor = 'calculated'; diff --git a/packages/mermaid/src/themes/theme-default.js b/packages/mermaid/src/themes/theme-default.js index c91029de3..339bfa48a 100644 --- a/packages/mermaid/src/themes/theme-default.js +++ b/packages/mermaid/src/themes/theme-default.js @@ -109,9 +109,8 @@ class Theme { this.todayLineColor = 'red'; /* C4 Context Diagram variables */ - - this.personBorder = 'calculated'; - this.personBkg = 'calculated'; + this.personBorder = this.primaryBorderColor; + this.personBkg = this.mainBkg; /* state colors */ this.labelColor = 'black'; diff --git a/packages/mermaid/src/themes/theme-forest.js b/packages/mermaid/src/themes/theme-forest.js index 96d6c35c1..3d47684bc 100644 --- a/packages/mermaid/src/themes/theme-forest.js +++ b/packages/mermaid/src/themes/theme-forest.js @@ -83,9 +83,8 @@ class Theme { this.todayLineColor = 'red'; /* C4 Context Diagram variables */ - - this.personBorder = 'calculated'; - this.personBkg = 'calculated'; + this.personBorder = this.primaryBorderColor; + this.personBkg = this.mainBkg; /* state colors */ this.labelColor = 'black'; diff --git a/packages/mermaid/src/themes/theme-neutral.js b/packages/mermaid/src/themes/theme-neutral.js index 8bb5ff693..9dce63b8d 100644 --- a/packages/mermaid/src/themes/theme-neutral.js +++ b/packages/mermaid/src/themes/theme-neutral.js @@ -95,9 +95,8 @@ class Theme { this.todayLineColor = 'calculated'; /* C4 Context Diagram variables */ - - this.personBorder = 'calculated'; - this.personBkg = 'calculated'; + this.personBorder = this.primaryBorderColor; + this.personBkg = this.mainBkg; /* state colors */ this.labelColor = 'black'; From 9cb7a4a3f59d8489e43bef3a58248ec5e040ccfb Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 05:58:47 +0100 Subject: [PATCH 03/10] fix: fix invalid CSS `fill-opacity` value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix an invalid value for the CSS `fill-opacity` value. Percentage values for `fill-opacity` are only supported in the SVG 2.0 draft, so according to [MDN][1]: > it is not widely supported yet, […] as a consequence, it is best > practices [sic] to set opacity with a value in the range `[0-1]`. [1]: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-opacity --- packages/mermaid/src/diagrams/requirement/styles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/requirement/styles.js b/packages/mermaid/src/diagrams/requirement/styles.js index d0579d204..9db0fa00a 100644 --- a/packages/mermaid/src/diagrams/requirement/styles.js +++ b/packages/mermaid/src/diagrams/requirement/styles.js @@ -16,7 +16,7 @@ const getStyles = (options) => ` .reqBox { fill: ${options.requirementBackground}; - fill-opacity: 100%; + fill-opacity: 1.0; stroke: ${options.requirementBorderColor}; stroke-width: ${options.requirementBorderSize}; } @@ -26,7 +26,7 @@ const getStyles = (options) => ` } .reqLabelBox { fill: ${options.relationLabelBackground}; - fill-opacity: 100%; + fill-opacity: 1.0; } .req-title-line { From 7566b5620eadffa383410fe8bfa3429ab14db2e6 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 06:09:41 +0100 Subject: [PATCH 04/10] fix: fix `requirementBorderSize` theme variable Currently, `requirementBorderSize` defaults to `primaryBorderColor`, which is a color, not a valid SVG `stroke-width`. Instead, I've made it default to `1`. --- packages/mermaid/src/themes/theme-base.js | 2 +- packages/mermaid/src/themes/theme-dark.js | 2 +- packages/mermaid/src/themes/theme-default.js | 2 +- packages/mermaid/src/themes/theme-forest.js | 2 +- packages/mermaid/src/themes/theme-neutral.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mermaid/src/themes/theme-base.js b/packages/mermaid/src/themes/theme-base.js index 01f8a9c0b..a3d4a738f 100644 --- a/packages/mermaid/src/themes/theme-base.js +++ b/packages/mermaid/src/themes/theme-base.js @@ -219,7 +219,7 @@ class Theme { /* requirement-diagram */ this.requirementBackground = this.requirementBackground || this.primaryColor; this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor; - this.requirementBorderSize = this.requirementBorderSize || this.primaryBorderColor; + this.requirementBorderSize = this.requirementBorderSize || '1'; this.requirementTextColor = this.requirementTextColor || this.primaryTextColor; this.relationColor = this.relationColor || this.lineColor; this.relationLabelBackground = diff --git a/packages/mermaid/src/themes/theme-dark.js b/packages/mermaid/src/themes/theme-dark.js index 6ee26169d..8fde494bd 100644 --- a/packages/mermaid/src/themes/theme-dark.js +++ b/packages/mermaid/src/themes/theme-dark.js @@ -231,7 +231,7 @@ class Theme { /* requirement-diagram */ this.requirementBackground = this.requirementBackground || this.primaryColor; this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor; - this.requirementBorderSize = this.requirementBorderSize || this.primaryBorderColor; + this.requirementBorderSize = this.requirementBorderSize || '1'; this.requirementTextColor = this.requirementTextColor || this.primaryTextColor; this.relationColor = this.relationColor || this.lineColor; this.relationLabelBackground = diff --git a/packages/mermaid/src/themes/theme-default.js b/packages/mermaid/src/themes/theme-default.js index 339bfa48a..460a1b920 100644 --- a/packages/mermaid/src/themes/theme-default.js +++ b/packages/mermaid/src/themes/theme-default.js @@ -250,7 +250,7 @@ class Theme { /* requirement-diagram */ this.requirementBackground = this.requirementBackground || this.primaryColor; this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor; - this.requirementBorderSize = this.requirementBorderSize || this.primaryBorderColor; + this.requirementBorderSize = this.requirementBorderSize || '1'; this.requirementTextColor = this.requirementTextColor || this.primaryTextColor; this.relationColor = this.relationColor || this.lineColor; this.relationLabelBackground = this.relationLabelBackground || this.labelBackground; diff --git a/packages/mermaid/src/themes/theme-forest.js b/packages/mermaid/src/themes/theme-forest.js index 3d47684bc..527c6a38c 100644 --- a/packages/mermaid/src/themes/theme-forest.js +++ b/packages/mermaid/src/themes/theme-forest.js @@ -219,7 +219,7 @@ class Theme { /* requirement-diagram */ this.requirementBackground = this.requirementBackground || this.primaryColor; this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor; - this.requirementBorderSize = this.requirementBorderSize || this.primaryBorderColor; + this.requirementBorderSize = this.requirementBorderSize || '1'; this.requirementTextColor = this.requirementTextColor || this.primaryTextColor; this.relationColor = this.relationColor || this.lineColor; this.relationLabelBackground = this.relationLabelBackground || this.edgeLabelBackground; diff --git a/packages/mermaid/src/themes/theme-neutral.js b/packages/mermaid/src/themes/theme-neutral.js index 9dce63b8d..15c3d1d2d 100644 --- a/packages/mermaid/src/themes/theme-neutral.js +++ b/packages/mermaid/src/themes/theme-neutral.js @@ -249,7 +249,7 @@ class Theme { /* requirement-diagram */ this.requirementBackground = this.requirementBackground || this.primaryColor; this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor; - this.requirementBorderSize = this.requirementBorderSize || this.primaryBorderColor; + this.requirementBorderSize = this.requirementBorderSize || '1'; this.requirementTextColor = this.requirementTextColor || this.primaryTextColor; this.relationColor = this.relationColor || this.lineColor; this.relationLabelBackground = this.relationLabelBackground || this.edgeLabelBackground; From 54f827d850de40148201d4fcf9c8f41907fbada4 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 06:24:20 +0100 Subject: [PATCH 05/10] fix: define `arrowheadColor` for `theme-base` Define `arrowheadColor` as `invert(this.background)` in `theme-base.js`, as it's currently `undefined`, which causes CSS issues when using `theme-base`. I've picked `invert(this.background)` so that it matches the default value of `lineColor`. --- packages/mermaid/src/themes/theme-base.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mermaid/src/themes/theme-base.js b/packages/mermaid/src/themes/theme-base.js index a3d4a738f..ba95843b6 100644 --- a/packages/mermaid/src/themes/theme-base.js +++ b/packages/mermaid/src/themes/theme-base.js @@ -46,6 +46,7 @@ class Theme { this.secondaryTextColor = this.secondaryTextColor || invert(this.secondaryColor); this.tertiaryTextColor = this.tertiaryTextColor || invert(this.tertiaryColor); this.lineColor = this.lineColor || invert(this.background); + this.arrowheadColor = this.arrowheadColor || invert(this.background); this.textColor = this.textColor || this.primaryTextColor; /* Flowchart variables */ From 720408e1439d4d537849aa58687a3da6bb2379b5 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 06:27:09 +0100 Subject: [PATCH 06/10] fix: define `border2` for `theme-base` `border2` is a theme variable used by the CSS for flowcharts and user-journey. I've defined this to default to `tertiaryBorderColor` in theme-base, as other themes tend to set `border2` to the same value as `clusterBorder`, which in theme-base is `tertiaryBorderColor`. --- packages/mermaid/src/themes/theme-base.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/mermaid/src/themes/theme-base.js b/packages/mermaid/src/themes/theme-base.js index ba95843b6..f3da6f1a4 100644 --- a/packages/mermaid/src/themes/theme-base.js +++ b/packages/mermaid/src/themes/theme-base.js @@ -49,6 +49,9 @@ class Theme { this.arrowheadColor = this.arrowheadColor || invert(this.background); this.textColor = this.textColor || this.primaryTextColor; + // TODO: should this instead default to secondaryBorderColor? + this.border2 = this.border2 || this.tertiaryBorderColor; + /* Flowchart variables */ this.nodeBkg = this.nodeBkg || this.primaryColor; this.mainBkg = this.mainBkg || this.primaryColor; From 4f9c4548bf696a76020eef26b2491173d7f36e83 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 07:08:32 +0100 Subject: [PATCH 07/10] fix: define `excludeBkgColor` for `theme-dark` Define `excludeBkgColor` for `theme-dark` to fix invalid CSS for gantt diagrams. All the other themes defined this to '#eeeeee', but I thought that was a bit too bright in a dark theme, so instead I set it to `darken(this.sectionBkgColor, 10);`. --- packages/mermaid/src/themes/theme-dark.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mermaid/src/themes/theme-dark.js b/packages/mermaid/src/themes/theme-dark.js index 8fde494bd..b0bc6ff5b 100644 --- a/packages/mermaid/src/themes/theme-dark.js +++ b/packages/mermaid/src/themes/theme-dark.js @@ -64,6 +64,7 @@ class Theme { this.sectionBkgColor = darken('#EAE8D9', 30); this.altSectionBkgColor = 'calculated'; this.sectionBkgColor2 = '#EAE8D9'; + this.excludeBkgColor = darken(this.sectionBkgColor, 10); this.taskBorderColor = rgba(255, 255, 255, 70); this.taskBkgColor = 'calculated'; this.taskTextColor = 'calculated'; From cd976871f028636e5380f6ae207e63cb9b8eeac5 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 07:12:40 +0100 Subject: [PATCH 08/10] fix: define `gitBranchLabel`* in theme forest/dark Define `gitBranchLabel[0-7]` in `theme-dark` and `theme-forest` to fix invalid CSS for gitgraphs. The values have been copied from [`theme-default`][1]). [1]: https://github.com/mermaid-js/mermaid/blob/727bf30824e08c672c7f13ed83f111c2d1596935/packages/mermaid/src/themes/theme-default.js#L296-L303 --- packages/mermaid/src/themes/theme-dark.js | 8 ++++++++ packages/mermaid/src/themes/theme-forest.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/mermaid/src/themes/theme-dark.js b/packages/mermaid/src/themes/theme-dark.js index b0bc6ff5b..201a380a1 100644 --- a/packages/mermaid/src/themes/theme-dark.js +++ b/packages/mermaid/src/themes/theme-dark.js @@ -257,6 +257,14 @@ class Theme { this.gitInv5 = this.gitInv5 || invert(this.git5); this.gitInv6 = this.gitInv6 || invert(this.git6); this.gitInv7 = this.gitInv7 || invert(this.git7); + this.gitBranchLabel0 = this.gitBranchLabel0 || invert(this.labelTextColor); + this.gitBranchLabel1 = this.gitBranchLabel1 || this.labelTextColor; + this.gitBranchLabel2 = this.gitBranchLabel2 || this.labelTextColor; + this.gitBranchLabel3 = this.gitBranchLabel3 || invert(this.labelTextColor); + this.gitBranchLabel4 = this.gitBranchLabel4 || this.labelTextColor; + this.gitBranchLabel5 = this.gitBranchLabel5 || this.labelTextColor; + this.gitBranchLabel6 = this.gitBranchLabel6 || this.labelTextColor; + this.gitBranchLabel7 = this.gitBranchLabel7 || this.labelTextColor; this.tagLabelColor = this.tagLabelColor || this.primaryTextColor; this.tagLabelBackground = this.tagLabelBackground || this.primaryColor; diff --git a/packages/mermaid/src/themes/theme-forest.js b/packages/mermaid/src/themes/theme-forest.js index 527c6a38c..67fc557a9 100644 --- a/packages/mermaid/src/themes/theme-forest.js +++ b/packages/mermaid/src/themes/theme-forest.js @@ -261,6 +261,14 @@ class Theme { this.gitInv5 = this.gitInv5 || invert(this.git5); this.gitInv6 = this.gitInv6 || invert(this.git6); this.gitInv7 = this.gitInv7 || invert(this.git7); + this.gitBranchLabel0 = this.gitBranchLabel0 || invert(this.labelTextColor); + this.gitBranchLabel1 = this.gitBranchLabel1 || this.labelTextColor; + this.gitBranchLabel2 = this.gitBranchLabel2 || this.labelTextColor; + this.gitBranchLabel3 = this.gitBranchLabel3 || invert(this.labelTextColor); + this.gitBranchLabel4 = this.gitBranchLabel4 || this.labelTextColor; + this.gitBranchLabel5 = this.gitBranchLabel5 || this.labelTextColor; + this.gitBranchLabel6 = this.gitBranchLabel6 || this.labelTextColor; + this.gitBranchLabel7 = this.gitBranchLabel7 || this.labelTextColor; this.tagLabelColor = this.tagLabelColor || this.primaryTextColor; this.tagLabelBackground = this.tagLabelBackground || this.primaryColor; From aee18ca018cd1c863de802db18308a53b8d29d5e Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 13 Apr 2023 07:25:18 +0100 Subject: [PATCH 09/10] fix: fix `scaleLabelColor` in theme forest/neutral The `scaleLabelColor` variable in `theme-forest` and `theme-neutral` was set to `"calculated"`, as it defaults to `this.labelTextColor` **before** `this.labelTextColor` was set. Moving the `this.labelTextColor` assignments before `scaleLabelColor` is calculated fixes this. Fixes mindmap and timeline invalid CSS in theme forest and neutral. --- packages/mermaid/src/themes/theme-forest.js | 19 ++++++------ packages/mermaid/src/themes/theme-neutral.js | 32 ++++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/mermaid/src/themes/theme-forest.js b/packages/mermaid/src/themes/theme-forest.js index 67fc557a9..c6eb9574c 100644 --- a/packages/mermaid/src/themes/theme-forest.js +++ b/packages/mermaid/src/themes/theme-forest.js @@ -93,6 +93,15 @@ class Theme { this.errorTextColor = '#552222'; } updateColors() { + /* Sequence Diagram variables */ + this.actorBorder = darken(this.mainBkg, 20); + this.actorBkg = this.mainBkg; + this.labelBoxBkgColor = this.actorBkg; + this.labelTextColor = this.actorTextColor; + this.loopTextColor = this.actorTextColor; + this.noteBorderColor = this.border2; + this.noteTextColor = this.actorTextColor; + /* Each color-set will have a background, a foreground and a border color */ this.cScale0 = this.cScale0 || this.primaryColor; this.cScale1 = this.cScale1 || this.secondaryColor; @@ -144,16 +153,6 @@ class Theme { this.clusterBorder = this.border2; this.defaultLinkColor = this.lineColor; - /* Sequence Diagram variables */ - - this.actorBorder = darken(this.mainBkg, 20); - this.actorBkg = this.mainBkg; - this.labelBoxBkgColor = this.actorBkg; - this.labelTextColor = this.actorTextColor; - this.loopTextColor = this.actorTextColor; - this.noteBorderColor = this.border2; - this.noteTextColor = this.actorTextColor; - /* Gantt chart variables */ this.taskBorderColor = this.border1; diff --git a/packages/mermaid/src/themes/theme-neutral.js b/packages/mermaid/src/themes/theme-neutral.js index 15c3d1d2d..d03d7be1d 100644 --- a/packages/mermaid/src/themes/theme-neutral.js +++ b/packages/mermaid/src/themes/theme-neutral.js @@ -108,6 +108,22 @@ class Theme { this.secondBkg = lighten(this.contrast, 55); this.border2 = this.contrast; + /* Sequence Diagram variables */ + + this.actorBorder = lighten(this.border1, 23); + this.actorBkg = this.mainBkg; + this.actorTextColor = this.text; + this.actorLineColor = this.lineColor; + this.signalColor = this.text; + this.signalTextColor = this.text; + this.labelBoxBkgColor = this.actorBkg; + this.labelBoxBorderColor = this.actorBorder; + this.labelTextColor = this.text; + this.loopTextColor = this.text; + this.noteBorderColor = '#999'; + this.noteBkgColor = '#666'; + this.noteTextColor = '#fff'; + /* Color Scale */ /* Each color-set will have a background, a foreground and a border color */ @@ -161,22 +177,6 @@ class Theme { this.defaultLinkColor = this.lineColor; this.titleColor = this.text; - /* Sequence Diagram variables */ - - this.actorBorder = lighten(this.border1, 23); - this.actorBkg = this.mainBkg; - this.actorTextColor = this.text; - this.actorLineColor = this.lineColor; - this.signalColor = this.text; - this.signalTextColor = this.text; - this.labelBoxBkgColor = this.actorBkg; - this.labelBoxBorderColor = this.actorBorder; - this.labelTextColor = this.text; - this.loopTextColor = this.text; - this.noteBorderColor = '#999'; - this.noteBkgColor = '#666'; - this.noteTextColor = '#fff'; - /* Gantt chart variables */ this.sectionBkgColor = lighten(this.contrast, 30); From b4164b6ab59b22d41a6066b93d821783c4b4788b Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Wed, 12 Apr 2023 07:08:06 +0100 Subject: [PATCH 10/10] test: test that styles and themes return valid CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test that `src/diagrams/*/styles.ts` module returns a valid CSS stylesheet that can be parsed via [stylis][1] and then becomes a valid CSS that [csstree-validator][2] validates. We test this for every diagram and for every theme, because many of the invalid CSS bugs are caused by missing theme vars. There are some CSS errors that I couldn't easily fix, so I've written the tests to ignore the following CSS errors: - 'Unknown property `rx`' (Valid in SVG2 draft and in some browsers) - 'Unknown property `ry`' (Valid in SVG2 draft and in some browsers) - 'Unknown property `dy`' - This doesn't seem to be valid CSS in any SVG version, but maybe some browsers support it 🤷 I feel like we should probably change this though. [1]: https://github.com/thysultan/stylis [2]: https://github.com/csstree/validator --- packages/mermaid/package.json | 1 + packages/mermaid/src/styles.spec.ts | 120 ++++++++++++++++++++++++++++ pnpm-lock.yaml | 45 ++++++++++- 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 packages/mermaid/src/styles.spec.ts diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 02edc874b..12da8c2ff 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -87,6 +87,7 @@ "coveralls": "^3.1.1", "cpy-cli": "^4.2.0", "cspell": "^6.14.3", + "csstree-validator": "^3.0.0", "globby": "^13.1.2", "jison": "^0.4.18", "js-base64": "^3.7.2", diff --git a/packages/mermaid/src/styles.spec.ts b/packages/mermaid/src/styles.spec.ts new file mode 100644 index 000000000..07997ad60 --- /dev/null +++ b/packages/mermaid/src/styles.spec.ts @@ -0,0 +1,120 @@ +import { vi } from 'vitest'; + +// @ts-expect-error This module has no TypeScript types +import { validate } from 'csstree-validator'; +import { compile, serialize, stringify } from 'stylis'; + +import { getConfig } from './config'; +import theme from './themes'; + +/** + * Import the getStyles function from each diagram. + * + * Unfortunately, we can't use the `diagrams/*?/*Detector.ts` functions, + * because many of the diagrams have a circular dependency import error + * (they import mermaidAPI.js, which imports diagramOrchestrator.js, which causes a loop) + */ +import c4 from './diagrams/c4/styles'; +import classDiagram from './diagrams/class/styles'; +import flowchart from './diagrams/flowchart/styles'; +import flowchartElk from './diagrams/flowchart/elk/styles'; +import er from './diagrams/er/styles'; +import error from './diagrams/error/styles'; +import git from './diagrams/git/styles'; +import gantt from './diagrams/gantt/styles'; +import info from './diagrams/info/styles'; +import pie from './diagrams/pie/styles'; +import requirement from './diagrams/requirement/styles'; +import sequence from './diagrams/sequence/styles'; +import state from './diagrams/state/styles'; +import journey from './diagrams/user-journey/styles'; +import timeline from './diagrams/timeline/styles'; +import mindmap from './diagrams/mindmap/styles'; +import themes from './themes'; + +async function checkValidStylisCSSStyleSheet(stylisString: string) { + const cssString = serialize(compile(`#my-svg-id{${stylisString}}`), stringify); + const errors = validate(cssString, 'this-file-was-created-by-tests.css') as Error[]; + + const unexpectedErrors = errors.filter((error) => { + const cssErrorsToIgnore = [ + // Valid in SVG2, see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/rx + // Ideally, we'd remove this, since some browsers do not support SVG2. + 'Unknown property `rx`', + // Valid in SVG2, see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/ry + 'Unknown property `ry`', + // TODO: I'm pretty sure that even in SVG2, this isn't allowed to be a CSS + // attribute. + 'Unknown property `dy`', + ]; + return !cssErrorsToIgnore.some((cssErrorToIgnore) => error.message.match(cssErrorToIgnore)); + }); + + if (unexpectedErrors.length > 0) { + throw new Error( + `The given CSS string was invalid: ${errors}.\n\n` + + 'Copy the below CSS into https://jigsaw.w3.org/css-validator/validator to help debug where the invalid CSS is:\n\n' + + `Original CSS value was ${cssString}` + ); + } +} + +describe('styles', () => { + beforeEach(() => { + // resets the styles added to addStylesForDiagram() + vi.resetModules(); + }); + + describe('getStyles', () => { + test('should return a valid style for an empty type', async () => { + const { default: getStyles, addStylesForDiagram } = await import('./styles'); + + const diagramType = 'my-custom-mocked-type-with-no-styles'; + const myTypeGetStylesFunc = vi.fn().mockReturnValue(''); + + addStylesForDiagram(diagramType, myTypeGetStylesFunc); + + const styles = getStyles(diagramType, '', getConfig().themeVariables); + + await checkValidStylisCSSStyleSheet(styles); + }); + + /** + * Test CSS for each diagram type and each theme. + */ + for (const themeId of Object.keys(theme) as (keyof typeof theme)[]) { + for (const [diagramId, getDiagramStyles] of Object.entries({ + c4, + classDiagram, + er, + error, + flowchart, + flowchartElk, + gantt, + git, + info, + journey, + mindmap, + pie, + requirement, + sequence, + state, + timeline, + })) { + test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => { + const { default: getStyles, addStylesForDiagram } = await import('./styles'); + + addStylesForDiagram(diagramId, getDiagramStyles); + const styles = getStyles( + diagramId, + '', + // @ts-expect-error This will probably be broken until we create a proper Themes type. + themes[themeId].getThemeVariables() + ); + + await checkValidStylisCSSStyleSheet(styles); + }); + } + } + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdac6ed51..d3e978513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -138,6 +138,7 @@ importers: coveralls: ^3.1.1 cpy-cli: ^4.2.0 cspell: ^6.14.3 + csstree-validator: ^3.0.0 cytoscape: ^3.23.0 cytoscape-cose-bilkent: ^4.1.0 cytoscape-fcose: ^2.1.0 @@ -206,6 +207,7 @@ importers: coveralls: 3.1.1 cpy-cli: 4.2.0 cspell: 6.14.3 + csstree-validator: 3.0.0 globby: 13.1.2 jison: 0.4.18 js-base64: 3.7.2 @@ -4065,7 +4067,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2_debug@4.3.4 + follow-redirects: 1.15.2_debug@4.3.2 transitivePeerDependencies: - debug dev: true @@ -4509,6 +4511,13 @@ packages: jsonlint: 1.6.0 dev: true + /clap/3.1.1: + resolution: {integrity: sha512-vp42956Ax06WwaaheYEqEOgXZ3VKJxgccZ0gJL0HpyiupkIS9RVJFo5eDU1BPeQAOqz+cclndZg4DCqG1sJReQ==} + engines: {node: ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + ansi-colors: 4.1.3 + dev: true + /clean-regexp/1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -5142,6 +5151,14 @@ packages: source-map: 0.6.1 dev: true + /css-tree/2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + /cssom/0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} dev: true @@ -5157,6 +5174,16 @@ packages: cssom: 0.3.8 dev: true + /csstree-validator/3.0.0: + resolution: {integrity: sha512-Y5OSq3wI0Xz6L7DCgJQtQ97U+v99SkX9r663VjpvUMJPhEr0A149OxiAGqcnokB5bt81irgnMudspBzujzqn0w==} + engines: {node: ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + hasBin: true + dependencies: + clap: 3.1.1 + css-tree: 2.3.1 + resolve: 1.22.1 + dev: true + /csstype/2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} dev: true @@ -6641,6 +6668,18 @@ packages: resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==} dev: true + /follow-redirects/1.15.2_debug@4.3.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.2 + dev: true + /follow-redirects/1.15.2_debug@4.3.4: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -8728,6 +8767,10 @@ packages: resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==} dev: true + /mdn-data/2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + /mdn-data/2.0.6: resolution: {integrity: sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==} dev: true