mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-25 10:20:06 +02:00
test: test that styles and themes return valid CSS
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
This commit is contained in:
@@ -87,6 +87,7 @@
|
|||||||
"coveralls": "^3.1.1",
|
"coveralls": "^3.1.1",
|
||||||
"cpy-cli": "^4.2.0",
|
"cpy-cli": "^4.2.0",
|
||||||
"cspell": "^6.14.3",
|
"cspell": "^6.14.3",
|
||||||
|
"csstree-validator": "^3.0.0",
|
||||||
"globby": "^13.1.2",
|
"globby": "^13.1.2",
|
||||||
"jison": "^0.4.18",
|
"jison": "^0.4.18",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
|
120
packages/mermaid/src/styles.spec.ts
Normal file
120
packages/mermaid/src/styles.spec.ts
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
45
pnpm-lock.yaml
generated
45
pnpm-lock.yaml
generated
@@ -138,6 +138,7 @@ importers:
|
|||||||
coveralls: ^3.1.1
|
coveralls: ^3.1.1
|
||||||
cpy-cli: ^4.2.0
|
cpy-cli: ^4.2.0
|
||||||
cspell: ^6.14.3
|
cspell: ^6.14.3
|
||||||
|
csstree-validator: ^3.0.0
|
||||||
cytoscape: ^3.23.0
|
cytoscape: ^3.23.0
|
||||||
cytoscape-cose-bilkent: ^4.1.0
|
cytoscape-cose-bilkent: ^4.1.0
|
||||||
cytoscape-fcose: ^2.1.0
|
cytoscape-fcose: ^2.1.0
|
||||||
@@ -206,6 +207,7 @@ importers:
|
|||||||
coveralls: 3.1.1
|
coveralls: 3.1.1
|
||||||
cpy-cli: 4.2.0
|
cpy-cli: 4.2.0
|
||||||
cspell: 6.14.3
|
cspell: 6.14.3
|
||||||
|
csstree-validator: 3.0.0
|
||||||
globby: 13.1.2
|
globby: 13.1.2
|
||||||
jison: 0.4.18
|
jison: 0.4.18
|
||||||
js-base64: 3.7.2
|
js-base64: 3.7.2
|
||||||
@@ -4065,7 +4067,7 @@ packages:
|
|||||||
/axios/0.21.4_debug@4.3.2:
|
/axios/0.21.4_debug@4.3.2:
|
||||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2_debug@4.3.4
|
follow-redirects: 1.15.2_debug@4.3.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4509,6 +4511,13 @@ packages:
|
|||||||
jsonlint: 1.6.0
|
jsonlint: 1.6.0
|
||||||
dev: true
|
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:
|
/clean-regexp/1.0.0:
|
||||||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -5142,6 +5151,14 @@ packages:
|
|||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
dev: true
|
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:
|
/cssom/0.3.8:
|
||||||
resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
|
resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -5157,6 +5174,16 @@ packages:
|
|||||||
cssom: 0.3.8
|
cssom: 0.3.8
|
||||||
dev: true
|
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:
|
/csstype/2.6.21:
|
||||||
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
|
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6641,6 +6668,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
|
resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
|
||||||
dev: true
|
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:
|
/follow-redirects/1.15.2_debug@4.3.4:
|
||||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -8728,6 +8767,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
|
resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mdn-data/2.0.30:
|
||||||
|
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/mdn-data/2.0.6:
|
/mdn-data/2.0.6:
|
||||||
resolution: {integrity: sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==}
|
resolution: {integrity: sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
Reference in New Issue
Block a user