mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08: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,
|
||||
} 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:**
|
||||
|
||||
|
@ -80,7 +80,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### 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
|
||||
|
||||
@ -111,7 +111,7 @@ Return the last node appended
|
||||
|
||||
#### 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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[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';
|
||||
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 { MermaidConfig } from './config.type';
|
||||
|
||||
@ -37,7 +69,10 @@ vi.mock('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'];
|
||||
|
||||
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);
|
||||
htmlElements.forEach((htmlElement) => {
|
||||
@ -373,7 +409,7 @@ describe('mermaidAPI', function () {
|
||||
const htmlElements = ['rect', 'polygon', 'ellipse', 'circle'];
|
||||
|
||||
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);
|
||||
htmlElements.forEach((htmlElement) => {
|
||||
@ -510,7 +546,7 @@ describe('mermaidAPI', function () {
|
||||
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();
|
||||
expect(orgConfig.testObject).toBe(undefined);
|
||||
|
||||
@ -616,6 +652,7 @@ describe('mermaidAPI', function () {
|
||||
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dompurify config', function () {
|
||||
it('allows dompurify config to be set', function () {
|
||||
mermaidAPI.initialize({ dompurifyConfig: { ADD_ATTR: ['onclick'] } });
|
||||
@ -623,6 +660,7 @@ describe('mermaidAPI', function () {
|
||||
expect(mermaidAPI!.getConfig()!.dompurifyConfig!.ADD_ATTR).toEqual(['onclick']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse', function () {
|
||||
mermaid.parseError = undefined; // ensure it parseError undefined
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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 { MermaidConfig } from './config.type';
|
||||
import { evaluate } from './diagrams/common/common';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility';
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
// diagram names that support classDef statements
|
||||
@ -487,12 +489,13 @@ const render = function (
|
||||
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 graphType = diag.type;
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// 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
|
||||
const svg = element.firstChild;
|
||||
@ -509,6 +512,7 @@ const render = function (
|
||||
idSelector
|
||||
);
|
||||
|
||||
// svg is a HTML element (not a d3 node)
|
||||
const style1 = document.createElement('style');
|
||||
style1.innerHTML = `${idSelector} ` + rules;
|
||||
svg.insertBefore(style1, firstChild);
|
||||
@ -522,6 +526,13 @@ const render = function (
|
||||
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
|
||||
root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD);
|
||||
@ -763,7 +774,7 @@ const renderAsync = async function (
|
||||
attachFunctions();
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Remove the temporary element if appropriate
|
||||
// Remove the temporary HTML element if appropriate
|
||||
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
||||
const node = select(tmpElementSelector).node();
|
||||
if (node && 'remove' in node) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user