mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-26 10:49:38 +02:00
put a11y into mermaidAPI render; add render spec (mock diagram renderers etc)
This commit is contained in:

parent
f62c5d9698
commit
29efc116f3
@@ -214,7 +214,9 @@ The functions for setting title and description are provided by a common module.
|
|||||||
clear as commonClear,
|
clear as commonClear,
|
||||||
} from '../../commonDb';
|
} from '../../commonDb';
|
||||||
|
|
||||||
For rendering the accessibility tags you have again an existing function you can use.
|
Starting with Mermaid version, the accessibility tags are inserted into the SVG element in the `render` function in mermaidAPI.
|
||||||
|
|
||||||
|
In version \_\_\_ and before, you need to insert the accessibility tags in your renderer:
|
||||||
|
|
||||||
**In the renderer:**
|
**In the renderer:**
|
||||||
|
|
||||||
|
@@ -80,7 +80,7 @@ mermaid.initialize(config);
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:949](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L949)
|
[mermaidAPI.ts:960](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L960)
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ Return the last node appended
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:292](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L292)
|
[mermaidAPI.ts:294](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L294)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ the cleaned up svgCode
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:243](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L243)
|
[mermaidAPI.ts:245](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L245)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ the string with all the user styles
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L170)
|
[mermaidAPI.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L172)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ the string with all the user styles
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:220](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L220)
|
[mermaidAPI.ts:222](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L222)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L154)
|
[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L128)
|
[mermaidAPI.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L130)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:99](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L99)
|
[mermaidAPI.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L101)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -279,7 +279,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:271](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L271)
|
[mermaidAPI.ts:273](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L273)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -305,4 +305,4 @@ Remove any existing elements from the given document
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[mermaidAPI.ts:343](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L343)
|
[mermaidAPI.ts:345](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L345)
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
// Mocks and mocking
|
||||||
|
|
||||||
|
import { MockedD3 } from './tests/MockedD3';
|
||||||
|
|
||||||
|
// Note: If running this directly from within an IDE, the mocks directory must be at packages/mermaid/mocks
|
||||||
|
vi.mock('d3');
|
||||||
|
vi.mock('dagre-d3');
|
||||||
|
|
||||||
|
// mermaidAPI.spec.ts:
|
||||||
|
import * as accessibility from './accessibility'; // Import it this way so we can use spyOn(accessibility,...)
|
||||||
|
vi.mock('./accessibility', () => ({
|
||||||
|
setA11yDiagramInfo: vi.fn(),
|
||||||
|
addSVGa11yTitleDescription: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the renderers specifically so we can test render(). Need to mock draw() for each renderer
|
||||||
|
vi.mock('./diagrams/c4/c4Renderer');
|
||||||
|
vi.mock('./diagrams/class/classRenderer');
|
||||||
|
vi.mock('./diagrams/class/classRenderer-v2');
|
||||||
|
vi.mock('./diagrams/er/erRenderer');
|
||||||
|
vi.mock('./diagrams/flowchart/flowRenderer-v2');
|
||||||
|
vi.mock('./diagrams/git/gitGraphRenderer');
|
||||||
|
vi.mock('./diagrams/gantt/ganttRenderer');
|
||||||
|
vi.mock('./diagrams/user-journey/journeyRenderer');
|
||||||
|
vi.mock('./diagrams/pie/pieRenderer');
|
||||||
|
vi.mock('./diagrams/requirement/requirementRenderer');
|
||||||
|
vi.mock('./diagrams/sequence/sequenceRenderer');
|
||||||
|
vi.mock('./diagrams/state/stateRenderer-v2');
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
import mermaid from './mermaid';
|
import mermaid from './mermaid';
|
||||||
import { MermaidConfig } from './config.type';
|
import { MermaidConfig } from './config.type';
|
||||||
|
|
||||||
@@ -37,7 +69,10 @@ vi.mock('stylis', () => {
|
|||||||
});
|
});
|
||||||
import { compile, serialize } from 'stylis';
|
import { compile, serialize } from 'stylis';
|
||||||
|
|
||||||
import { MockedD3 } from './tests/MockedD3';
|
/**
|
||||||
|
* @see https://vitest.dev/guide/mocking.html Mock part of a module
|
||||||
|
* To investigate how to mock just some methods from a module - call the actual implementation and then mock others, e.g. so they can be spied on
|
||||||
|
*/
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -335,7 +370,8 @@ describe('mermaidAPI', function () {
|
|||||||
const htmlElements = ['> *', 'span'];
|
const htmlElements = ['> *', 'span'];
|
||||||
|
|
||||||
it('creates CSS styles for every style and textStyle in every classDef', () => {
|
it('creates CSS styles for every style and textStyle in every classDef', () => {
|
||||||
// @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result
|
// @todo TODO Can't figure out how to spy on the cssImportantStyles method.
|
||||||
|
// That would be a much better approach than manually checking the result
|
||||||
|
|
||||||
const styles = createCssStyles(mocked_config, graphType, classDefs);
|
const styles = createCssStyles(mocked_config, graphType, classDefs);
|
||||||
htmlElements.forEach((htmlElement) => {
|
htmlElements.forEach((htmlElement) => {
|
||||||
@@ -373,7 +409,7 @@ describe('mermaidAPI', function () {
|
|||||||
const htmlElements = ['rect', 'polygon', 'ellipse', 'circle'];
|
const htmlElements = ['rect', 'polygon', 'ellipse', 'circle'];
|
||||||
|
|
||||||
it('creates CSS styles for every style and textStyle in every classDef', () => {
|
it('creates CSS styles for every style and textStyle in every classDef', () => {
|
||||||
// @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result
|
// TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result.
|
||||||
|
|
||||||
const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs);
|
const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs);
|
||||||
htmlElements.forEach((htmlElement) => {
|
htmlElements.forEach((htmlElement) => {
|
||||||
@@ -510,7 +546,7 @@ describe('mermaidAPI', function () {
|
|||||||
expect(config.testLiteral).toBe(true);
|
expect(config.testLiteral).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copies a an object into the configuration', function () {
|
it('copies an object into the configuration', function () {
|
||||||
const orgConfig: any = mermaidAPI.getConfig();
|
const orgConfig: any = mermaidAPI.getConfig();
|
||||||
expect(orgConfig.testObject).toBe(undefined);
|
expect(orgConfig.testObject).toBe(undefined);
|
||||||
|
|
||||||
@@ -616,6 +652,7 @@ describe('mermaidAPI', function () {
|
|||||||
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('dompurify config', function () {
|
describe('dompurify config', function () {
|
||||||
it('allows dompurify config to be set', function () {
|
it('allows dompurify config to be set', function () {
|
||||||
mermaidAPI.initialize({ dompurifyConfig: { ADD_ATTR: ['onclick'] } });
|
mermaidAPI.initialize({ dompurifyConfig: { ADD_ATTR: ['onclick'] } });
|
||||||
@@ -623,6 +660,7 @@ describe('mermaidAPI', function () {
|
|||||||
expect(mermaidAPI!.getConfig()!.dompurifyConfig!.ADD_ATTR).toEqual(['onclick']);
|
expect(mermaidAPI!.getConfig()!.dompurifyConfig!.ADD_ATTR).toEqual(['onclick']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parse', function () {
|
describe('parse', function () {
|
||||||
mermaid.parseError = undefined; // ensure it parseError undefined
|
mermaid.parseError = undefined; // ensure it parseError undefined
|
||||||
it('throws for an invalid definition (with no mermaid.parseError() defined)', function () {
|
it('throws for an invalid definition (with no mermaid.parseError() defined)', function () {
|
||||||
@@ -646,4 +684,54 @@ describe('mermaidAPI', function () {
|
|||||||
expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true);
|
expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('render', () => {
|
||||||
|
// These are more like integration tests right now because nothing is mocked.
|
||||||
|
// But it is faster that a cypress test and there's no real reason to actually evaluate an image pixel by pixel.
|
||||||
|
|
||||||
|
// render(id, text, cb?, svgContainingElement?)
|
||||||
|
|
||||||
|
// Test all diagram types. Note that old flowchart 'graph' type will invoke the flowRenderer-v2. (See the flowchart v2 detector.)
|
||||||
|
// We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams)
|
||||||
|
const diagramTypesAndExpectations = [
|
||||||
|
{ textDiagramType: 'C4Context', expectedType: 'c4' },
|
||||||
|
{ textDiagramType: 'classDiagram', expectedType: 'classDiagram' },
|
||||||
|
{ textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' },
|
||||||
|
{ textDiagramType: 'erDiagram', expectedType: 'er' },
|
||||||
|
{ textDiagramType: 'graph', expectedType: 'flowchart-v2' },
|
||||||
|
{ textDiagramType: 'flowchart', expectedType: 'flowchart-v2' },
|
||||||
|
{ textDiagramType: 'gitGraph', expectedType: 'gitGraph' },
|
||||||
|
{ textDiagramType: 'gantt', expectedType: 'gantt' },
|
||||||
|
{ textDiagramType: 'journey', expectedType: 'journey' },
|
||||||
|
{ textDiagramType: 'pie', expectedType: 'pie' },
|
||||||
|
{ textDiagramType: 'requirementDiagram', expectedType: 'requirement' },
|
||||||
|
{ textDiagramType: 'sequenceDiagram', expectedType: 'sequence' },
|
||||||
|
{ textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('accessibility', () => {
|
||||||
|
const id = 'mermaid-fauxId';
|
||||||
|
const a11yTitle = 'a11y title';
|
||||||
|
const a11yDescr = 'a11y description';
|
||||||
|
|
||||||
|
diagramTypesAndExpectations.forEach((testedDiagram) => {
|
||||||
|
describe(`${testedDiagram.textDiagramType}`, () => {
|
||||||
|
const diagramType = testedDiagram.textDiagramType;
|
||||||
|
const diagramText = `${diagramType}\n accTitle: ${a11yTitle}\n accDescr: ${a11yDescr}\n`;
|
||||||
|
const expectedDiagramType = testedDiagram.expectedType;
|
||||||
|
|
||||||
|
it('aria-roledscription is set to the diagram type, addSVGa11yTitleDescription is called', () => {
|
||||||
|
const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo');
|
||||||
|
const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription');
|
||||||
|
mermaidAPI.render(id, diagramText);
|
||||||
|
expect(a11yDiagramInfo_spy).toHaveBeenCalledWith(
|
||||||
|
expect.anything(),
|
||||||
|
expectedDiagramType
|
||||||
|
);
|
||||||
|
expect(a11yTitleDesc_spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -29,6 +29,8 @@ import utils, { directiveSanitizer } from './utils';
|
|||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import { MermaidConfig } from './config.type';
|
import { MermaidConfig } from './config.type';
|
||||||
import { evaluate } from './diagrams/common/common';
|
import { evaluate } from './diagrams/common/common';
|
||||||
|
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility';
|
||||||
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
// diagram names that support classDef statements
|
// diagram names that support classDef statements
|
||||||
@@ -487,12 +489,13 @@ const render = function (
|
|||||||
parseEncounteredException = error;
|
parseEncounteredException = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the temporary div element containing the svg
|
// Get the temporary div element containing the svg (the parent HTML Element)
|
||||||
const element = root.select(enclosingDivID_selector).node();
|
const element = root.select(enclosingDivID_selector).node();
|
||||||
const graphType = diag.type;
|
const graphType = diag.type;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
// Create and insert the styles (user styles, theme styles, config styles)
|
// Create and insert the styles (user styles, theme styles, config styles)
|
||||||
|
// These are dealing with HTML Elements, not d3 nodes.
|
||||||
|
|
||||||
// Insert an element into svg. This is where we put the styles
|
// Insert an element into svg. This is where we put the styles
|
||||||
const svg = element.firstChild;
|
const svg = element.firstChild;
|
||||||
@@ -509,6 +512,7 @@ const render = function (
|
|||||||
idSelector
|
idSelector
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// svg is a HTML element (not a d3 node)
|
||||||
const style1 = document.createElement('style');
|
const style1 = document.createElement('style');
|
||||||
style1.innerHTML = `${idSelector} ` + rules;
|
style1.innerHTML = `${idSelector} ` + rules;
|
||||||
svg.insertBefore(style1, firstChild);
|
svg.insertBefore(style1, firstChild);
|
||||||
@@ -522,6 +526,13 @@ const render = function (
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the d3 node for the svg element
|
||||||
|
const svgNode = root.select(`${enclosingDivID_selector} svg`);
|
||||||
|
setA11yDiagramInfo(svgNode, graphType);
|
||||||
|
const a11yTitle = diag.db.getAccTitle !== undefined ? diag.db.getAccTitle() : null;
|
||||||
|
const a11yDescr = diag.db.getAccDescription !== undefined ? diag.db.getAccDescription() : null;
|
||||||
|
addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id'));
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
// Clean up SVG code
|
// Clean up SVG code
|
||||||
root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD);
|
root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD);
|
||||||
@@ -763,7 +774,7 @@ const renderAsync = async function (
|
|||||||
attachFunctions();
|
attachFunctions();
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------
|
||||||
// Remove the temporary element if appropriate
|
// Remove the temporary HTML element if appropriate
|
||||||
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
||||||
const node = select(tmpElementSelector).node();
|
const node = select(tmpElementSelector).node();
|
||||||
if (node && 'remove' in node) {
|
if (node && 'remove' in node) {
|
||||||
|
Reference in New Issue
Block a user