Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and test a bunch of invalid CSS issues #4295

Merged
merged 10 commits into from
Apr 13, 2023
1 change: 1 addition & 0 deletions packages/mermaid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/class/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const getStyles = (options) =>

.divider {
stroke: ${options.nodeBorder};
stroke: 1;
stroke-width: 1;
}

g.clickable {
Expand Down
4 changes: 2 additions & 2 deletions packages/mermaid/src/diagrams/requirement/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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};
}
Expand All @@ -26,7 +26,7 @@ const getStyles = (options) => `
}
.reqLabelBox {
fill: ${options.relationLabelBackground};
fill-opacity: 100%;
fill-opacity: 1.0;
}

.req-title-line {
Expand Down
120 changes: 120 additions & 0 deletions packages/mermaid/src/styles.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
}
});
});
6 changes: 5 additions & 1 deletion packages/mermaid/src/themes/theme-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ 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;

// 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;
Expand Down Expand Up @@ -219,7 +223,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 =
Expand Down
16 changes: 12 additions & 4 deletions packages/mermaid/src/themes/theme-dark.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -81,9 +82,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';
Expand Down Expand Up @@ -232,7 +232,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 =
Expand All @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions packages/mermaid/src/themes/theme-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -251,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;
Expand Down
34 changes: 20 additions & 14 deletions packages/mermaid/src/themes/theme-forest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -94,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;
Expand Down Expand Up @@ -145,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;
Expand Down Expand Up @@ -220,7 +218,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;
Expand Down Expand Up @@ -262,6 +260,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;
Expand Down
39 changes: 19 additions & 20 deletions packages/mermaid/src/themes/theme-neutral.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -109,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 */

Expand Down Expand Up @@ -162,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);
Expand Down Expand Up @@ -250,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;
Expand Down
Loading