From 8d96518092ce582cbce38c391d2cb760eb33ecef Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 15 Nov 2022 13:47:16 -0800 Subject: [PATCH 01/27] accessibility.js -> ts; + set aria-roledescription; add spec --- packages/mermaid/src/accessibility.js | 29 --------- packages/mermaid/src/accessibility.spec.ts | 73 ++++++++++++++++++++++ packages/mermaid/src/accessibility.ts | 45 +++++++++++++ 3 files changed, 118 insertions(+), 29 deletions(-) delete mode 100644 packages/mermaid/src/accessibility.js create mode 100644 packages/mermaid/src/accessibility.spec.ts create mode 100644 packages/mermaid/src/accessibility.ts diff --git a/packages/mermaid/src/accessibility.js b/packages/mermaid/src/accessibility.js deleted file mode 100644 index c59ba270c..000000000 --- a/packages/mermaid/src/accessibility.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * This method will add a basic title and description element to a chart. The yy parser will need to - * respond to getAccTitle and getAccDescription, where the title is the title element on the chart, - * which is generally not displayed and the accDescription is the description element on the chart, - * which is never displayed. - * - * The following charts display their title as a visual and accessibility element: gantt - * - * @param yy_parser - * @param svg - * @param id - */ -export default function addSVGAccessibilityFields(yy_parser, svg, id) { - if (typeof svg.insert === 'undefined') { - return; - } - - let title_string = yy_parser.getAccTitle(); - let description = yy_parser.getAccDescription(); - svg.attr('role', 'img').attr('aria-labelledby', 'chart-title-' + id + ' chart-desc-' + id); - svg - .insert('desc', ':first-child') - .attr('id', 'chart-desc-' + id) - .text(description); - svg - .insert('title', ':first-child') - .attr('id', 'chart-title-' + id) - .text(title_string); -} diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts new file mode 100644 index 000000000..29a2f125d --- /dev/null +++ b/packages/mermaid/src/accessibility.spec.ts @@ -0,0 +1,73 @@ +// Spec/tests for accessibility + +import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; + +import { MockedD3 } from './tests/MockedD3'; + +const fauxSvgNode = new MockedD3(); + +const MockedDiagramDb = { + getAccTitle: vi.fn().mockReturnValue('the title'), + getAccDescription: vi.fn().mockReturnValue('the description'), +}; + +describe('setA11yDiagramInfo', () => { + it('sets the aria-roledescription to the diagram type', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); + }); +}); + +describe('addSVGa11yTitleDescription', () => { + const testDiagramDb = MockedDiagramDb; + const givenId = 'theBaseId'; + + describe('with the given svg d3 object:', () => { + it('does nothing if there is no insert defined', () => { + const noInsertSvg = { + attr: vi.fn(), + }; + const noInsert_attr_spy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); + addSVGa11yTitleDescription(testDiagramDb, noInsertSvg, givenId); + expect(noInsert_attr_spy).not.toHaveBeenCalled(); + }); + + it('sets aria-labelledby to the title id and the description id inserted as children', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith( + 'aria-labelledby', + `chart-title-${givenId} chart-desc-${givenId}` + ); + }); + + it('inserts a title tag as the first child with the text set to the accTitle returned by the diagram db', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + // @ts-ignore Required to easily handle the d3 select types + const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); + const title_text_spy = vi.spyOn(faux_title, 'text'); + + addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('title', ':first-child'); + expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-title-` + givenId); + expect(title_text_spy).toHaveBeenNthCalledWith(2, 'the title'); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription returned by the diagram db', () => { + const faux_desc = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); + // @ts-ignore Required to easily handle the d3 select types + const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); + const desc_text_spy = vi.spyOn(faux_desc, 'text'); + + addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); + expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); + expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'the description'); + }); + }); +}); diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts new file mode 100644 index 000000000..246a88f66 --- /dev/null +++ b/packages/mermaid/src/accessibility.ts @@ -0,0 +1,45 @@ +/** + * Accessibility (a11y) functions, types, helpers + * + */ + +// This is just a convenience alias to make it clear the type is a d3 object. (It's easier to make it 'any' instead of the comple typing set in d3) +type D3object = any; + +/** + * Set the accessibility (a11y) information for the svg d3 object using the given diagram type + * Note that the svg element role _should_ be mapped to a 'graphics-document' by default. Thus we don't set it here, but can set it in the future if needed. + * @param svg - d3 object that contains the SVG HTML element + * @param diagramType - diagram name for to the aria-roledescription + */ +export function setA11yDiagramInfo(svg: D3object, diagramType: string) { + svg.attr('aria-roledescription', diagramType); +} + +/** + * This method will add a basic title and description element to a chart. The yy parser will need to + * respond to getAccTitle and getAccDescription, + * where the accessible title is the title element on the chart. + * + * Note that the accessible title is generally _not_ displayed + * and the accessible description is never displayed. + * + * + * The following charts display their title as a visual and accessibility element: gantt. TODO fix this + * + * @param diagramDb - the 'db' object/module for a diagram. Must respond to getAccTitle() and getAccDescription() + * @param svg - the d3 object that represents the svg element + * @param baseId - the id to use as the base for the title and description + */ +export function addSVGa11yTitleDescription(diagramDb: any, svg: D3object, baseId: string) { + if (typeof svg.insert === 'undefined') { + return; + } + + const titleId = 'chart-title-' + baseId; + const descId = 'chart-desc-' + baseId; + + svg.attr('aria-labelledby', titleId + ' ' + descId); + svg.insert('desc', ':first-child').attr('id', descId).text(diagramDb.getAccDescription()); + svg.insert('title', ':first-child').attr('id', titleId).text(diagramDb.getAccTitle()); +} From 0d9566dd711374a35fc872f4a327d4a518dfd04b Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 15 Nov 2022 13:48:35 -0800 Subject: [PATCH 02/27] diagrams: use a11y title,desc specific method (was renamed) --- packages/mermaid/src/diagrams/c4/c4Renderer.js | 4 ++-- packages/mermaid/src/diagrams/class/classRenderer-v2.js | 4 ++-- packages/mermaid/src/diagrams/class/classRenderer.js | 4 ++-- packages/mermaid/src/diagrams/er/erRenderer.js | 4 ++-- packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js | 4 ++-- packages/mermaid/src/diagrams/flowchart/flowRenderer.js | 4 ++-- packages/mermaid/src/diagrams/gantt/ganttRenderer.js | 4 ++-- packages/mermaid/src/diagrams/git/gitGraphRenderer.js | 4 ++-- packages/mermaid/src/diagrams/pie/pieRenderer.js | 4 ++-- .../mermaid/src/diagrams/requirement/requirementRenderer.js | 4 ++-- packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts | 4 +++- packages/mermaid/src/diagrams/state/stateRenderer-v2.js | 4 ++-- packages/mermaid/src/diagrams/state/stateRenderer.js | 4 ++-- packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts | 3 ++- 14 files changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/mermaid/src/diagrams/c4/c4Renderer.js b/packages/mermaid/src/diagrams/c4/c4Renderer.js index a9072346a..a2d7813c6 100644 --- a/packages/mermaid/src/diagrams/c4/c4Renderer.js +++ b/packages/mermaid/src/diagrams/c4/c4Renderer.js @@ -8,7 +8,7 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let globalBoundaryMaxX = 0, globalBoundaryMaxY = 0; @@ -676,7 +676,7 @@ export const draw = function (_text, id, _version, diagObj) { (height + extraVertForTitle) ); - addSVGAccessibilityFields(parser.yy, diagram, id); + addSVGa11yTitleDescription(parser.yy, diagram, id); log.debug(`models:`, box); }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index fbc2e4833..75e7cdafb 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -7,7 +7,7 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let idCache = {}; @@ -452,7 +452,7 @@ export const draw = function (text, id, _version, diagObj) { } } - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); // If node has a link, wrap it in an anchor SVG object. // const keys = Object.keys(classes); // keys.forEach(function(key) { diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index 23b586192..4e4b31a82 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -5,7 +5,7 @@ import { log } from '../../logger'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let idCache = {}; const padding = 20; @@ -272,7 +272,7 @@ export const draw = function (text, id, _version, diagObj) { const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; log.debug(`viewBox ${vBox}`); diagram.attr('viewBox', vBox); - addSVGAccessibilityFields(diagObj.db, diagram, id); + addSVGa11yTitleDescription(diagObj.db, diagram, id); }; export default { diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 323bb4607..f29eb44d7 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -5,7 +5,7 @@ import { getConfig } from '../../config'; import { log } from '../../logger'; import erMarkers from './erMarkers'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; import { parseGenericTypes } from '../common/common'; import { v4 as uuid4 } from 'uuid'; @@ -657,7 +657,7 @@ export const draw = function (text, id, _version, diagObj) { svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); }; // draw /** diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index 6b7c4c1bf..a5f991a86 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -10,7 +10,7 @@ import { log } from '../../logger'; import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -431,7 +431,7 @@ export const draw = function (text, id, _version, diagObj) { const svg = root.select(`[id="${id}"]`); // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index c403b7fe3..e9069ff4d 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -8,7 +8,7 @@ import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import flowChartShapes from './flowChartShapes'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -418,7 +418,7 @@ export const draw = function (text, id, _version, diagObj) { const svg = root.select(`[id="${id}"]`); // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index 9501dd024..dc92be1f0 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -19,7 +19,7 @@ import { import common from '../common/common'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); @@ -116,7 +116,7 @@ export const draw = function (text, id, version, diagObj) { .attr('y', conf.titleTopMargin) .attr('class', 'titleText'); - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); /** * @param tasks diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 71698a500..c9e02a1e4 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -1,7 +1,7 @@ import { select } from 'd3'; import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI'; import { log } from '../../logger'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let allCommitsDict = {}; @@ -513,7 +513,7 @@ export const draw = function (txt, id, ver, diagObj) { const diagram = select(`[id="${id}"]`); // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, diagram, id); + addSVGa11yTitleDescription(diagObj.db, diagram, id); drawCommits(diagram, allCommitsDict, false); if (gitGraphConfig.showBranches) { diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js index 6cbb99fa3..daddfda86 100644 --- a/packages/mermaid/src/diagrams/pie/pieRenderer.js +++ b/packages/mermaid/src/diagrams/pie/pieRenderer.js @@ -3,7 +3,7 @@ import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import * as configApi from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let conf = configApi.getConfig(); @@ -53,7 +53,7 @@ export const draw = (txt, id, _version, diagObj) => { const diagram = root.select('#' + id); configureSvgSize(diagram, height, width, conf.pie.useMaxWidth); - addSVGAccessibilityFields(diagObj.db, diagram, id); + addSVGa11yTitleDescription(diagObj.db, diagram, id); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index 79d67e76e..60f456f0b 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -6,7 +6,7 @@ import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import markers from './requirementMarkers'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; let conf = {}; let relCnt = 0; @@ -364,7 +364,7 @@ export const draw = (text, id, _version, diagObj) => { svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); // Adds title and description to the requirements diagram - addSVGAccessibilityFields(diagObj.db, svg, id); + addSVGa11yTitleDescription(diagObj.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index fa943d658..bf8a512c1 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -9,9 +9,11 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; import Diagram from '../../Diagram'; +// FIXME insert a11y title and desc + let conf = {}; export const bounds = { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 1011fb6b1..d6bc821c4 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -5,7 +5,7 @@ import { render } from '../../dagre-wrapper/index.js'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; import { DEFAULT_DIAGRAM_DIRECTION, DEFAULT_NESTED_DOC_DIR, @@ -481,7 +481,7 @@ export const draw = function (text, id, _version, diag) { label.insertBefore(rect, label.firstChild); // } } - addSVGAccessibilityFields(diag.db, svg, id); + addSVGa11yTitleDescription(diag.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer.js b/packages/mermaid/src/diagrams/state/stateRenderer.js index 73717a645..b69d1aaee 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer.js @@ -6,7 +6,7 @@ import common from '../common/common'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; // TODO Move conf object to main conf in mermaidAPI let conf; @@ -97,7 +97,7 @@ export const draw = function (text, id, _version, diagObj) { 'viewBox', `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height ); - addSVGAccessibilityFields(diagObj.db, diagram, id); + addSVGa11yTitleDescription(diagObj.db, diagram, id); }; const getLabelWidth = (text) => { return text ? text.length * conf.fontSizeFactor : 1; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index 3880a243a..5a2c1283d 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts +++ b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts @@ -3,7 +3,8 @@ import { select } from 'd3'; import svgDraw from './svgDraw'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; +import { addSVGa11yTitleDescription } from '../../accessibility'; +// FIXME insert a11y title and desc export const setConf = function (cnf) { const keys = Object.keys(cnf); From d99707641b33908d87bb18896a3945213f165c08 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Tue, 15 Nov 2022 13:49:05 -0800 Subject: [PATCH 03/27] add "roledescription" to cSpell list of words (as in 'aria-roledescription') --- cSpell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cSpell.json b/cSpell.json index 08fce1d1c..3398361f4 100644 --- a/cSpell.json +++ b/cSpell.json @@ -60,6 +60,7 @@ "podlite", "ranksep", "redmine", + "roledescription", "sandboxed", "setupgraphviewbox", "shiki", From 03a11e103ec119660bf2e6b3a3294464591d0935 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:19:31 -0800 Subject: [PATCH 04/27] (minor) fix typo --- cypress/integration/rendering/stateDiagram-v2.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 5b43c890c..adba68ed2 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -328,7 +328,7 @@ describe('State diagram', () => { } ); }); - it('v2 it should be possibel to use a choice', () => { + it('v2 it should be possible to use a choice', () => { imgSnapshotTest( ` stateDiagram-v2 From 4d7496b8dd74a758186e54745e1d05dfc66a2000 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:21:45 -0800 Subject: [PATCH 05/27] add error checking (empty diagramType, title, desc) to a11y methods --- packages/mermaid/src/accessibility.spec.ts | 219 +++++++++++++++------ packages/mermaid/src/accessibility.ts | 59 +++--- 2 files changed, 194 insertions(+), 84 deletions(-) diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts index 29a2f125d..7336284fe 100644 --- a/packages/mermaid/src/accessibility.spec.ts +++ b/packages/mermaid/src/accessibility.spec.ts @@ -1,73 +1,172 @@ -// Spec/tests for accessibility - +import { MockedD3 } from './tests/MockedD3'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; -import { MockedD3 } from './tests/MockedD3'; +describe('accessibility', () => { + const fauxSvgNode = new MockedD3(); -const fauxSvgNode = new MockedD3(); - -const MockedDiagramDb = { - getAccTitle: vi.fn().mockReturnValue('the title'), - getAccDescription: vi.fn().mockReturnValue('the description'), -}; - -describe('setA11yDiagramInfo', () => { - it('sets the aria-roledescription to the diagram type', () => { - // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); - setA11yDiagramInfo(fauxSvgNode, 'flowchart'); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); - }); -}); - -describe('addSVGa11yTitleDescription', () => { - const testDiagramDb = MockedDiagramDb; - const givenId = 'theBaseId'; - - describe('with the given svg d3 object:', () => { - it('does nothing if there is no insert defined', () => { - const noInsertSvg = { - attr: vi.fn(), - }; - const noInsert_attr_spy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); - addSVGa11yTitleDescription(testDiagramDb, noInsertSvg, givenId); - expect(noInsert_attr_spy).not.toHaveBeenCalled(); - }); - - it('sets aria-labelledby to the title id and the description id inserted as children', () => { - // @ts-ignore Required to easily handle the d3 select types + describe('setA11yDiagramInfo', () => { + it('sets the aria-roledescription to the diagram type', () => { + // @ts-ignore Required to easily handle the d3 select types const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); - addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith( - 'aria-labelledby', - `chart-title-${givenId} chart-desc-${givenId}` - ); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); }); - it('inserts a title tag as the first child with the text set to the accTitle returned by the diagram db', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + it('does nothing if the diagram type is empty', () => { // @ts-ignore Required to easily handle the d3 select types - const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); - const title_text_spy = vi.spyOn(faux_title, 'text'); - - addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('title', ':first-child'); - expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-title-` + givenId); - expect(title_text_spy).toHaveBeenNthCalledWith(2, 'the title'); + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, ''); + expect(svg_attr_spy).not.toHaveBeenCalled(); }); + }); - it('inserts a desc tag as the 2nd child with the text set to accDescription returned by the diagram db', () => { - const faux_desc = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); - // @ts-ignore Required to easily handle the d3 select types - const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); - const desc_text_spy = vi.spyOn(faux_desc, 'text'); + describe('addSVGa11yTitleDescription', () => { + const givenId = 'theBaseId'; - addSVGa11yTitleDescription(testDiagramDb, fauxSvgNode, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); - expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); - expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'the description'); + describe('with the given svg d3 object:', () => { + it('does nothing if there is no insert defined', () => { + const noInsertSvg = { + attr: vi.fn(), + }; + const noInsert_attr_spy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); + addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId); + expect(noInsert_attr_spy).not.toHaveBeenCalled(); + }); + + describe('given an a11y title', () => { + const a11yTitle = 'a11y title'; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('sets aria-labelledby to the title id and the description id inserted as children', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith( + 'aria-labelledby', + `chart-title-${givenId} chart-desc-${givenId}` + ); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + // @ts-ignore Required to easily handle the d3 select types + const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); + const title_text_spy = vi.spyOn(faux_title, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); + expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); + expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + const faux_desc = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); + // @ts-ignore Required to easily handle the d3 select types + const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); + const desc_text_spy = vi.spyOn(faux_desc, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); + expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); + expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + }); + }); + + describe(`no a11y description`, () => { + const a11yDesc = undefined; + + it('sets aria-labelledby to the title id inserted as a child', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + // @ts-ignore Required to easily handle the d3 select types + const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); + const title_text_spy = vi.spyOn(faux_title, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('title', ':first-child'); + expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-title-` + givenId); + expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y title'); + }); + + it('no description tag is inserted', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + }); + }); + + describe('no a11y title', () => { + const a11yTitle = undefined; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('no title tag inserted', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('sets aria-labelledby to the description id inserted as a child', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-desc-${givenId}`); + }); + + it('inserts a desc tag as a child with the text set to accDescription given', () => { + const faux_desc = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); + // @ts-ignore Required to easily handle the d3 select types + const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); + const desc_text_spy = vi.spyOn(faux_desc, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); + expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); + expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + }); + }); + + describe('no a11y description', () => { + const a11yDesc = undefined; + + it('no title tag inserted', () => { + const faux_title = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('no description tag inserted', () => { + const faux_desc = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).not.toHaveBeenCalled(); + }); + }); + }); }); }); }); diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts index 246a88f66..eff9a4edc 100644 --- a/packages/mermaid/src/accessibility.ts +++ b/packages/mermaid/src/accessibility.ts @@ -3,43 +3,54 @@ * */ -// This is just a convenience alias to make it clear the type is a d3 object. (It's easier to make it 'any' instead of the comple typing set in d3) +import { isEmpty, compact } from 'lodash'; + +// This is just a convenience alias to make it clear the type is a d3 object. (It's easier to make it 'any' instead of the complete typing set in d3) type D3object = any; /** - * Set the accessibility (a11y) information for the svg d3 object using the given diagram type - * Note that the svg element role _should_ be mapped to a 'graphics-document' by default. Thus we don't set it here, but can set it in the future if needed. + * Add aria-roledescription to the svg element to the diagramType + * * @param svg - d3 object that contains the SVG HTML element * @param diagramType - diagram name for to the aria-roledescription */ -export function setA11yDiagramInfo(svg: D3object, diagramType: string) { - svg.attr('aria-roledescription', diagramType); +export function setA11yDiagramInfo(svg: D3object, diagramType: string | null | undefined) { + if (!isEmpty(diagramType)) { + svg.attr('aria-roledescription', diagramType); + } } - /** - * This method will add a basic title and description element to a chart. The yy parser will need to - * respond to getAccTitle and getAccDescription, - * where the accessible title is the title element on the chart. + * Add an accessible title and/or description element to a chart. + * The title is usually not displayed and the description is never displayed. * - * Note that the accessible title is generally _not_ displayed - * and the accessible description is never displayed. + * The following charts display their title as a visual and accessibility element: gantt * - * - * The following charts display their title as a visual and accessibility element: gantt. TODO fix this - * - * @param diagramDb - the 'db' object/module for a diagram. Must respond to getAccTitle() and getAccDescription() - * @param svg - the d3 object that represents the svg element - * @param baseId - the id to use as the base for the title and description + * @param svg - d3 node to insert the a11y title and desc info + * @param a11yTitle - a11y title. null and undefined are meaningful: means to skip it + * @param a11yDesc - a11y description. null and undefined are meaningful: means to skip it + * @param baseId - id used to construct the a11y title and description id */ -export function addSVGa11yTitleDescription(diagramDb: any, svg: D3object, baseId: string) { +export function addSVGa11yTitleDescription( + svg: D3object, + a11yTitle: string | null | undefined, + a11yDesc: string | null | undefined, + baseId: string +) { if (typeof svg.insert === 'undefined') { return; } - const titleId = 'chart-title-' + baseId; - const descId = 'chart-desc-' + baseId; - - svg.attr('aria-labelledby', titleId + ' ' + descId); - svg.insert('desc', ':first-child').attr('id', descId).text(diagramDb.getAccDescription()); - svg.insert('title', ':first-child').attr('id', titleId).text(diagramDb.getAccTitle()); + const titleId = a11yTitle ? 'chart-title-' + baseId : null; + const descId = a11yDesc ? 'chart-desc-' + baseId : null; + if (a11yTitle || a11yDesc) { + svg.attr('aria-labelledby', compact([titleId, descId]).join(' ')); + if (a11yDesc) { + svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc); + } + if (a11yTitle) { + svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle); + } + } else { + return; + } } From 8a3c4f64b2d05a8173a879474c3e139d0be3fb0b Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:23:01 -0800 Subject: [PATCH 06/27] MockedD3: node() return Element; add selectAll() --- packages/mermaid/src/tests/MockedD3.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index d7c16b3a8..24cb9043f 100644 --- a/packages/mermaid/src/tests/MockedD3.ts +++ b/packages/mermaid/src/tests/MockedD3.ts @@ -1,12 +1,18 @@ /** * This is a mocked/stubbed version of the d3 Selection type. Each of the main functions are all * mocked (via vi.fn()) so you can track if they have been called, etc. + * + * Note that node() returns a HTML Element with tag 'svg'. It is an empty element (no innerHTML, no children, etc). + * This potentially allows testing of mermaidAPI render(). */ + export class MockedD3 { public attribs = new Map(); public id: string | undefined = ''; _children: MockedD3[] = []; + _containingHTMLdoc = new Document(); + constructor(givenId = 'mock-id') { this.id = givenId; } @@ -29,6 +35,11 @@ export class MockedD3 { return new MockedD3(cleanId); }); + // This has the same implementation as select(). (It calls it.) + selectAll = vi.fn().mockImplementation(({ select_str = '' }): MockedD3 => { + return this.select(select_str); + }); + append = vi .fn() .mockImplementation(function (this: MockedD3, type: string, id = '' + '-appended'): MockedD3 { @@ -87,9 +98,18 @@ export class MockedD3 { this.attribs.set('text', attrValue); return this; } - // NOTE: Arbitrarily returns an empty object. The return value could be something different with a mockReturnValue() or mockImplementation() - public node = vi.fn().mockReturnValue({}); + // NOTE: Returns a HTML ELement with tag 'svg' that has _another_ 'svg' element child. + // This allows different tests to succeed -- some need a top level 'svg' and some need a 'svg' element to be the firstChild + // Real implementation returns an HTML Element + public node = vi.fn().mockImplementation(() => { + const topElem = this._containingHTMLdoc.createElement('svg'); + const elem_svgChild = this._containingHTMLdoc.createElement('svg'); // another svg element + topElem.appendChild(elem_svgChild); + return topElem; + }); + + // TODO Is this correct? shouldn't it return a list of HTML Elements? nodes = vi.fn().mockImplementation(function (this: MockedD3): MockedD3[] { return this._children; }); From 1fc02940ae9fbb8c3f6ad892bd7202eed20638e8 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:24:16 -0800 Subject: [PATCH 07/27] move mocks specific to only seq spec files out of global d3 mock --- .../diagrams/sequence/sequenceDiagram.spec.js | 54 +++++++++++++++++++ .../src/diagrams/sequence/svgDraw.spec.js | 28 +++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 9422a5f37..6395940b0 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1,8 +1,62 @@ +import { vi } from 'vitest'; + import * as configApi from '../../config'; import mermaidAPI from '../../mermaidAPI'; import Diagram from '../../Diagram'; import { addDiagrams } from '../../diagram-api/diagram-orchestration'; + +/** + * Sequence diagrams require their own very special version of a mocked d3 module + * diagrams/sequence/svgDraw uses statements like this with d3 nodes: (note the [0][0]) + * + * // in drawText(...) + * textHeight += (textElem._groups || textElem)[0][0].getBBox().height; + */ +vi.mock('d3', () => { + const NewD3 = function () { + function returnThis() { + return this; + } + return { + append: function () { + return NewD3(); + }, + lower: returnThis, + attr: returnThis, + style: returnThis, + text: returnThis, + // [0][0] (below) is required by drawText() in packages/mermaid/src/diagrams/sequence/svgDraw.js + 0: { + 0: { + getBBox: function () { + return { + height: 10, + width: 20, + }; + }, + }, + }, + }; + }; + + return { + select: function () { + return new NewD3(); + }, + + selectAll: function () { + return new NewD3(); + }, + + curveBasis: 'basis', + curveLinear: 'linear', + curveCardinal: 'cardinal', + }; +}); +// ------------------------------- + addDiagrams(); + /** * @param conf * @param key diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 580dafe89..8e5f5f32b 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -1,5 +1,31 @@ +import { vi } from 'vitest'; import svgDraw from './svgDraw'; -import { MockD3 } from 'd3'; + +// This is the only place that uses this mock +export const MockD3 = (name, parent) => { + const children = []; + const elem = { + get __children() { + return children; + }, + get __name() { + return name; + }, + get __parent() { + return parent; + }, + }; + elem.append = (name) => { + const mockElem = MockD3(name, elem); + children.push(mockElem); + return mockElem; + }; + elem.lower = vi.fn(() => elem); + elem.attr = vi.fn(() => elem); + elem.text = vi.fn(() => elem); + elem.style = vi.fn(() => elem); + return elem; +}; describe('svgDraw', function () { describe('drawRect', function () { From 1ad537bc4d44025a361546aa45a8291a248b4049 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:24:58 -0800 Subject: [PATCH 08/27] d3 mock: use MockedD3; remove sequence specific mock code --- __mocks__/d3.ts | 59 +++---------------------------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/__mocks__/d3.ts b/__mocks__/d3.ts index 67f09b6f4..af35020c5 100644 --- a/__mocks__/d3.ts +++ b/__mocks__/d3.ts @@ -1,67 +1,14 @@ // @ts-nocheck TODO: Fix TS -import { vi } from 'vitest'; - -const NewD3 = function () { - /** - * - */ - function returnThis() { - return this; - } - return { - append: function () { - return NewD3(); - }, - lower: returnThis, - attr: returnThis, - style: returnThis, - text: returnThis, - 0: { - 0: { - getBBox: function () { - return { - height: 10, - width: 20, - }; - }, - }, - }, - }; -}; +import { MockedD3 } from '../packages/mermaid/src/tests/MockedD3'; export const select = function () { - return new NewD3(); + return new MockedD3(); }; export const selectAll = function () { - return new NewD3(); + return new MockedD3(); }; export const curveBasis = 'basis'; export const curveLinear = 'linear'; export const curveCardinal = 'cardinal'; - -export const MockD3 = (name, parent) => { - const children = []; - const elem = { - get __children() { - return children; - }, - get __name() { - return name; - }, - get __parent() { - return parent; - }, - }; - elem.append = (name) => { - const mockElem = MockD3(name, elem); - children.push(mockElem); - return mockElem; - }; - elem.lower = vi.fn(() => elem); - elem.attr = vi.fn(() => elem); - elem.text = vi.fn(() => elem); - elem.style = vi.fn(() => elem); - return elem; -}; From f62c5d9698d8738b7058ec8834c5d62e677d9178 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:25:14 -0800 Subject: [PATCH 09/27] add diagram renderer mocks --- __mocks__/c4Renderer.js | 21 +++++++++++++++++++++ __mocks__/classRenderer-v2.js | 16 ++++++++++++++++ __mocks__/classRenderer.js | 13 +++++++++++++ __mocks__/erRenderer.js | 16 ++++++++++++++++ __mocks__/flowRenderer-v2.js | 24 ++++++++++++++++++++++++ __mocks__/ganttRenderer.js | 16 ++++++++++++++++ __mocks__/gitGraphRenderer.js | 13 +++++++++++++ __mocks__/journeyRenderer.js | 15 +++++++++++++++ __mocks__/pieRenderer.js | 13 +++++++++++++ __mocks__/requirementRenderer.js | 13 +++++++++++++ __mocks__/sequenceRenderer.js | 23 +++++++++++++++++++++++ __mocks__/stateRenderer-v2.js | 22 ++++++++++++++++++++++ 12 files changed, 205 insertions(+) create mode 100644 __mocks__/c4Renderer.js create mode 100644 __mocks__/classRenderer-v2.js create mode 100644 __mocks__/classRenderer.js create mode 100644 __mocks__/erRenderer.js create mode 100644 __mocks__/flowRenderer-v2.js create mode 100644 __mocks__/ganttRenderer.js create mode 100644 __mocks__/gitGraphRenderer.js create mode 100644 __mocks__/journeyRenderer.js create mode 100644 __mocks__/pieRenderer.js create mode 100644 __mocks__/requirementRenderer.js create mode 100644 __mocks__/sequenceRenderer.js create mode 100644 __mocks__/stateRenderer-v2.js diff --git a/__mocks__/c4Renderer.js b/__mocks__/c4Renderer.js new file mode 100644 index 000000000..576d5d863 --- /dev/null +++ b/__mocks__/c4Renderer.js @@ -0,0 +1,21 @@ +/** + * Mocked C4Context diagram renderer + */ + +import { vi } from 'vitest'; + +export const drawPersonOrSystemArray = vi.fn(); +export const drawBoundary = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + drawPersonOrSystemArray, + drawBoundary, + setConf, + draw, +}; diff --git a/__mocks__/classRenderer-v2.js b/__mocks__/classRenderer-v2.js new file mode 100644 index 000000000..1ad95806f --- /dev/null +++ b/__mocks__/classRenderer-v2.js @@ -0,0 +1,16 @@ +/** + * Mocked class diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/classRenderer.js b/__mocks__/classRenderer.js new file mode 100644 index 000000000..1c20de4b1 --- /dev/null +++ b/__mocks__/classRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked class diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/erRenderer.js b/__mocks__/erRenderer.js new file mode 100644 index 000000000..845d641f7 --- /dev/null +++ b/__mocks__/erRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked er diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/flowRenderer-v2.js b/__mocks__/flowRenderer-v2.js new file mode 100644 index 000000000..89cc86031 --- /dev/null +++ b/__mocks__/flowRenderer-v2.js @@ -0,0 +1,24 @@ +/** + * Mocked flow (flowchart) diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const addVertices = vi.fn(); +export const addEdges = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + addVertices, + addEdges, + getClasses, + draw, +}; diff --git a/__mocks__/ganttRenderer.js b/__mocks__/ganttRenderer.js new file mode 100644 index 000000000..957249832 --- /dev/null +++ b/__mocks__/ganttRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked gantt diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/gitGraphRenderer.js b/__mocks__/gitGraphRenderer.js new file mode 100644 index 000000000..1daa82ca4 --- /dev/null +++ b/__mocks__/gitGraphRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked git (graph) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/journeyRenderer.js b/__mocks__/journeyRenderer.js new file mode 100644 index 000000000..2bc77c0b1 --- /dev/null +++ b/__mocks__/journeyRenderer.js @@ -0,0 +1,15 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/pieRenderer.js b/__mocks__/pieRenderer.js new file mode 100644 index 000000000..317c69901 --- /dev/null +++ b/__mocks__/pieRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/requirementRenderer.js b/__mocks__/requirementRenderer.js new file mode 100644 index 000000000..48d8997ac --- /dev/null +++ b/__mocks__/requirementRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked requirement diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/sequenceRenderer.js b/__mocks__/sequenceRenderer.js new file mode 100644 index 000000000..11080c6bb --- /dev/null +++ b/__mocks__/sequenceRenderer.js @@ -0,0 +1,23 @@ +/** + * Mocked sequence diagram renderer + */ + +import { vi } from 'vitest'; + +export const bounds = vi.fn(); +export const drawActors = vi.fn(); +export const drawActorsPopup = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + bounds, + drawActors, + drawActorsPopup, + setConf, + draw, +}; diff --git a/__mocks__/stateRenderer-v2.js b/__mocks__/stateRenderer-v2.js new file mode 100644 index 000000000..a2d103b50 --- /dev/null +++ b/__mocks__/stateRenderer-v2.js @@ -0,0 +1,22 @@ +/** + * Mocked state diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); +export const stateDomId = vi.fn().mockImplementation(() => { + return 'mocked-stateDiagram-stateDomId'; +}); +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + getClasses, + draw, +}; From 29efc116f340bc85fa670035b8d411ca957de3fe Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:27:17 -0800 Subject: [PATCH 10/27] put a11y into mermaidAPI render; add render spec (mock diagram renderers etc) --- docs/community/newDiagram.md | 4 +- docs/config/setup/modules/mermaidAPI.md | 20 +++--- packages/mermaid/src/mermaidAPI.spec.ts | 96 +++++++++++++++++++++++-- packages/mermaid/src/mermaidAPI.ts | 15 +++- 4 files changed, 118 insertions(+), 17 deletions(-) diff --git a/docs/community/newDiagram.md b/docs/community/newDiagram.md index da86f9838..e49dd3749 100644 --- a/docs/community/newDiagram.md +++ b/docs/community/newDiagram.md @@ -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:** diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 0acfe4f97..baa4a939c 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -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) diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 786b163c4..fcc547546 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -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(); + }); + }); + }); + }); + }); }); diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 0df1da305..18076b488 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -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) { From 0adc6a6112bca1d6461eb85ae7603b7fa7b3287f Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 12:28:11 -0800 Subject: [PATCH 11/27] remove a11y from individual diagrams; now happens in mermaidAPI render --- cypress/integration/rendering/gantt.spec.js | 32 --------- .../integration/rendering/requirement.spec.js | 65 ------------------- .../mermaid/src/diagrams/c4/c4Renderer.js | 2 - .../src/diagrams/class/classRenderer-v2.js | 2 - .../src/diagrams/class/classRenderer.js | 2 - .../mermaid/src/diagrams/er/erRenderer.js | 3 - .../src/diagrams/er/parser/erDiagram.spec.js | 16 ----- .../src/diagrams/flowchart/flowRenderer-v2.js | 4 -- .../src/diagrams/flowchart/flowRenderer.js | 4 -- .../src/diagrams/gantt/ganttRenderer.js | 3 - .../src/diagrams/git/gitGraphRenderer.js | 4 -- .../mermaid/src/diagrams/pie/pieRenderer.js | 2 - .../requirement/requirementRenderer.js | 3 - .../src/diagrams/sequence/sequenceRenderer.ts | 4 -- .../src/diagrams/state/stateRenderer-v2.js | 2 - .../src/diagrams/state/stateRenderer.js | 2 - .../diagrams/user-journey/journeyRenderer.ts | 4 -- 17 files changed, 154 deletions(-) diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index b75e682c6..c0156eee3 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -310,38 +310,6 @@ describe('Gantt diagram', () => { ); }); - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - gantt - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - dateFormat YYYY-MM-DD - section Section - A task :a1, 2014-01-01, 30d - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = Array.from(el.children); - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); - it('should render a gantt diagram with tick is 15 minutes', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/requirement.spec.js b/cypress/integration/rendering/requirement.spec.js index be27f39fa..0bf9014bf 100644 --- a/cypress/integration/rendering/requirement.spec.js +++ b/cypress/integration/rendering/requirement.spec.js @@ -46,69 +46,4 @@ describe('Requirement diagram', () => { ); cy.get('svg'); }); - - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - requirementDiagram - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - - requirement test_req { - id: 1 - text: the test text. - risk: high - verifymethod: test - } - - functionalRequirement test_req2 { - id: 1.1 - text: the second test text. - risk: low - verifymethod: inspection - } - - performanceRequirement test_req3 { - id: 1.2 - text: the third test text. - risk: medium - verifymethod: demonstration - } - - element test_entity { - type: simulation - } - - element test_entity2 { - type: word doc - docRef: reqs/test_entity - } - - - test_entity - satisfies -> test_req2 - test_req - traces -> test_req2 - test_req - contains -> test_req3 - test_req <- copies - test_entity2 - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = Array.from(el.children); - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); }); diff --git a/packages/mermaid/src/diagrams/c4/c4Renderer.js b/packages/mermaid/src/diagrams/c4/c4Renderer.js index a2d7813c6..580abbccc 100644 --- a/packages/mermaid/src/diagrams/c4/c4Renderer.js +++ b/packages/mermaid/src/diagrams/c4/c4Renderer.js @@ -8,7 +8,6 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let globalBoundaryMaxX = 0, globalBoundaryMaxY = 0; @@ -676,7 +675,6 @@ export const draw = function (_text, id, _version, diagObj) { (height + extraVertForTitle) ); - addSVGa11yTitleDescription(parser.yy, diagram, id); log.debug(`models:`, box); }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index 75e7cdafb..9c7002aa4 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -7,7 +7,6 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let idCache = {}; @@ -452,7 +451,6 @@ export const draw = function (text, id, _version, diagObj) { } } - addSVGa11yTitleDescription(diagObj.db, svg, id); // If node has a link, wrap it in an anchor SVG object. // const keys = Object.keys(classes); // keys.forEach(function(key) { diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index 4e4b31a82..b66222ccc 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -5,7 +5,6 @@ import { log } from '../../logger'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; import { getConfig } from '../../config'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let idCache = {}; const padding = 20; @@ -272,7 +271,6 @@ export const draw = function (text, id, _version, diagObj) { const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; log.debug(`viewBox ${vBox}`); diagram.attr('viewBox', vBox); - addSVGa11yTitleDescription(diagObj.db, diagram, id); }; export default { diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index f29eb44d7..ac32a0287 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -5,7 +5,6 @@ import { getConfig } from '../../config'; import { log } from '../../logger'; import erMarkers from './erMarkers'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; import { parseGenericTypes } from '../common/common'; import { v4 as uuid4 } from 'uuid'; @@ -656,8 +655,6 @@ export const draw = function (text, id, _version, diagObj) { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - - addSVGa11yTitleDescription(diagObj.db, svg, id); }; // draw /** diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index eb738fe4b..6131f7697 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -337,22 +337,6 @@ describe('when parsing ER diagram it...', function () { expect(erDb.getAccDescription()).toBe('this graph is about stuff'); }); - it('should allow for a accessibility title and multi line description (accDescr)', function () { - const teacherRole = 'is teacher of'; - const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; - - erDiagram.parser.parse( - `erDiagram - accTitle: graph title - accDescr { - this graph is about stuff - }\n - ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); - }); - it('should allow more than one relationship between the same two entities', function () { const line1 = 'CAR ||--o{ PERSON : "insured for"'; const line2 = 'CAR }o--|| PERSON : "owned by"'; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index a5f991a86..437c5a120 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -10,7 +10,6 @@ import { log } from '../../logger'; import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -430,9 +429,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGa11yTitleDescription(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index e9069ff4d..69eb8a9f3 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -8,7 +8,6 @@ import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import flowChartShapes from './flowChartShapes'; -import { addSVGa11yTitleDescription } from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -417,9 +416,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGa11yTitleDescription(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g); diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index dc92be1f0..9840cede4 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -19,7 +19,6 @@ import { import common from '../common/common'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); @@ -116,8 +115,6 @@ export const draw = function (text, id, version, diagObj) { .attr('y', conf.titleTopMargin) .attr('class', 'titleText'); - addSVGa11yTitleDescription(diagObj.db, svg, id); - /** * @param tasks * @param pageWidth diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index c9e02a1e4..fdd07059c 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -1,7 +1,6 @@ import { select } from 'd3'; import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI'; import { log } from '../../logger'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let allCommitsDict = {}; @@ -512,9 +511,6 @@ export const draw = function (txt, id, ver, diagObj) { const diagram = select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGa11yTitleDescription(diagObj.db, diagram, id); - drawCommits(diagram, allCommitsDict, false); if (gitGraphConfig.showBranches) { drawBranches(diagram, branches); diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js index daddfda86..d02f97f3f 100644 --- a/packages/mermaid/src/diagrams/pie/pieRenderer.js +++ b/packages/mermaid/src/diagrams/pie/pieRenderer.js @@ -3,7 +3,6 @@ import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import * as configApi from '../../config'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let conf = configApi.getConfig(); @@ -53,7 +52,6 @@ export const draw = (txt, id, _version, diagObj) => { const diagram = root.select('#' + id); configureSvgSize(diagram, height, width, conf.pie.useMaxWidth); - addSVGa11yTitleDescription(diagObj.db, diagram, id); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index 60f456f0b..9b5675adf 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -6,7 +6,6 @@ import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import markers from './requirementMarkers'; import { getConfig } from '../../config'; -import { addSVGa11yTitleDescription } from '../../accessibility'; let conf = {}; let relCnt = 0; @@ -363,8 +362,6 @@ export const draw = (text, id, _version, diagObj) => { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - // Adds title and description to the requirements diagram - addSVGa11yTitleDescription(diagObj.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index bf8a512c1..356b1d4c4 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -9,11 +9,8 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; import Diagram from '../../Diagram'; -// FIXME insert a11y title and desc - let conf = {}; export const bounds = { @@ -906,7 +903,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO (height + extraVertForTitle) ); - addSVGAccessibilityFields(diagObj.db, diagram, id); log.debug(`models:`, bounds.models); }; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index e3255bc65..ca0cf039c 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -5,7 +5,6 @@ import { render } from '../../dagre-wrapper/index.js'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; -import { addSVGa11yTitleDescription } from '../../accessibility'; import { DEFAULT_DIAGRAM_DIRECTION, DEFAULT_NESTED_DOC_DIR, @@ -472,7 +471,6 @@ export const draw = function (text, id, _version, diag) { label.insertBefore(rect, label.firstChild); // } } - addSVGa11yTitleDescription(diag.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer.js b/packages/mermaid/src/diagrams/state/stateRenderer.js index b69d1aaee..e4e882106 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer.js @@ -6,7 +6,6 @@ import common from '../common/common'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; // TODO Move conf object to main conf in mermaidAPI let conf; @@ -97,7 +96,6 @@ export const draw = function (text, id, _version, diagObj) { 'viewBox', `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height ); - addSVGa11yTitleDescription(diagObj.db, diagram, id); }; const getLabelWidth = (text) => { return text ? text.length * conf.fontSizeFactor : 1; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index 5a2c1283d..f3b6acb25 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts +++ b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts @@ -3,8 +3,6 @@ import { select } from 'd3'; import svgDraw from './svgDraw'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import { addSVGa11yTitleDescription } from '../../accessibility'; -// FIXME insert a11y title and desc export const setConf = function (cnf) { const keys = Object.keys(cnf); @@ -122,8 +120,6 @@ export const draw = function (text, id, version, diagObj) { diagram.attr('viewBox', `${box.startx} -25 ${width} ${height + extraVertForTitle}`); diagram.attr('preserveAspectRatio', 'xMinYMin meet'); diagram.attr('height', height + extraVertForTitle + 25); - - addSVGAccessibilityFields(diagObj.db, diagram, id); }; export const bounds = { From 4fb4aa417cb5633e81ce49c1b87d7a695f49a56e Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 15:45:28 -0800 Subject: [PATCH 12/27] doc: revise A11y: fix multi-line ex, +describedby, alpha sort examples --- docs/community/newDiagram.md | 46 +- docs/config/accessibility.md | 409 +++++++++++------- .../mermaid/src/docs/config/accessibility.md | 257 ++++++----- 3 files changed, 417 insertions(+), 295 deletions(-) diff --git a/docs/community/newDiagram.md b/docs/community/newDiagram.md index e49dd3749..288af42cd 100644 --- a/docs/community/newDiagram.md +++ b/docs/community/newDiagram.md @@ -14,8 +14,8 @@ This would be to define a jison grammar for the new diagram type. That should st For instance: -- the flowchart starts with the keyword graph. -- the sequence diagram starts with the keyword sequenceDiagram +- the flowchart starts with the keyword _graph_ +- the sequence diagram starts with the keyword _sequenceDiagram_ #### Store data found during parsing @@ -61,6 +61,11 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. + +Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. ### Step 4: The final piece - triggering the rendering @@ -168,17 +173,23 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility -The syntax for adding title and description looks like this: +Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: - accTitle: The title - accDescr: The description +- aria-roledescription +- accessible title +- accessible description - accDescr { - Syntax for a description text - written on multiple lines. - } +### aria-roledescription -In a similar way to the directives the jison syntax are quite similar between the diagrams. +The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. + +See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) in [the Accessible Rich Internet Applications W3 standard.](https://www.w3.org/WAI/standards-guidelines/aria/) + +### accessible title and description + +The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) + +In a similar way to the directives, the jison syntax are quite similar between the diagrams. ```jison @@ -214,20 +225,7 @@ The functions for setting title and description are provided by a common module. clear as commonClear, } from '../../commonDb'; -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:** - -```js -import addSVGAccessibilityFields from '../../accessibility'; - -/* ... */ - -// Adds title and description to the flow chart -addSVGAccessibilityFields(parser.yy, svg, id); -``` +The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. ## Theming diff --git a/docs/config/accessibility.md b/docs/config/accessibility.md index 8fa4aa3ac..e5b96670e 100644 --- a/docs/config/accessibility.md +++ b/docs/config/accessibility.md @@ -10,118 +10,169 @@ Now with Mermaid library in much wider use, we have started to work towards more accessible features, based on the feedback from the community. -To begin with, we have added a new feature to Mermaid library, which is to support accessibility options, **Accessibility Title** and **Accessibility Description**. +Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). +[Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -This support for accessibility options is available for all the diagrams/chart types. Also, we have tired to keep the same format for the accessibility options, so that it is easy to understand and maintain. +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) -## Defining Accessibility Options +### aria-roledescription -### Single line accessibility values +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) -The diagram authors can now add the accessibility options in the diagram definition, using the `accTitle` and `accDescr` keywords, where each keyword is followed by `:` and the string value for title and description like: +For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` +```html + +``` -**When these two options are defined, they will add a corresponding `` and `<desc>` tag in the SVG.** +### Accessible Title and Description -Let us take a look at the following example with a flowchart diagram: +Support for accessible titles and descriptions is available for all diagrams/chart types. We have tried to keep the same keywords and format for all diagrams so that it is easy to understand and maintain. + +The accessible title and description will add `<title>` and `<desc>` elements within the SVG element and the [aria-labelledby](https://www.w3.org/TR/wai-aria/#aria-labelledby) and [aria-describedby](https://www.w3.org/TR/wai-aria/#aria-describedby) attributes in the SVG tag. + +Here is HTML that is generated, showing that the SVG element is labelled by the accessible title (id = `chart-title-mermaid-1668725057758`) +and described by the accessible description (id = `chart-desc-mermaid-1668725057758` ); +and the accessible title element (text = "This is the accessible title") +and the accessible description element (text = "This is an accessible description"). + +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ + +```html +<svg + aria-labelledby="chart-title-mermaid-1668725057758" + aria-describedby="chart-desc-mermaid-1668725057758" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668725057758" +> + <title id="chart-title-mermaid-1668725057758">This is the accessible title + This is an accessible description + +``` + +Details for the syntax follow. + +#### accessible title + +The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. +The string value ends at the end of the line. (It can only be a single line.) + +Ex: `accTitle: This is a single line title` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accessible description + +An accessible description can be 1 line long (a single line) or many lines long. + +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. + +Ex: `accDescr: This is a single line description.` + +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). + +Ex: + + accDescr { The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: see the big decision and then make the big decision.} + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accTitle and accDescr Usage Examples + +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` ```mermaid - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) +```html + + Big decisions + Bob's Burgers process for making big decisions + +``` -### Multi-line Accessibility title/description - -You can also define the accessibility options in a multi-line format, where the keyword is followed by opening curly bracket `{` and then multiple lines, followed by a closing `}`. - -`accTitle: My single line title value` (**_single line format_**) - -vs - -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) - -Let us look at it in the following example, with same flowchart: +- Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision." ```mermaid-example - graph LR - accTitle: Big decisions - + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` ```mermaid - graph LR - accTitle: Big decisions - + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) - -### Sample Code Snippet for other diagram types - -#### Sequence Diagram - -```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description - - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! +```html + + Big decisions + + The official Bob's Burgers corporate processes that are used for making very, very big + decisions. This is actually a very simple flow: identify the big decision and then make the big + decision. + + ``` -```mermaid - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description +#### Sample Code Snippets for other diagram types - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! -``` - -#### Class Diagram +##### Class Diagram ```mermaid-example classDiagram @@ -139,27 +190,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the Vehicle <|-- Car ``` -#### State Diagram - -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -```mermaid - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -#### Entity Relationship Diagram +##### Entity Relationship Diagram ```mermaid-example erDiagram @@ -183,41 +214,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### User Journey Diagram - -```mermaid-example - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -```mermaid - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -#### Gantt Chart +##### Gantt Chart ```mermaid-example gantt @@ -251,7 +248,45 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Pie Chart +##### Gitgraph + +```mermaid-example + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +```mermaid + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +##### Pie Chart ```mermaid-example pie @@ -279,7 +314,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Requirement Diagram +##### Requirement Diagram ```mermaid-example requirementDiagram @@ -321,40 +356,78 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Gitgraph +##### Sequence Diagram ```mermaid-example - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +```mermaid + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description + + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +##### State Diagram + +```mermaid-example + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description + + s1 --> s2 ``` ```mermaid - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + s1 --> s2 + +``` + +##### User Journey Diagram + +```mermaid-example + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me + +``` + +```mermaid + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me ``` diff --git a/packages/mermaid/src/docs/config/accessibility.md b/packages/mermaid/src/docs/config/accessibility.md index ade20a839..5545ce1bc 100644 --- a/packages/mermaid/src/docs/config/accessibility.md +++ b/packages/mermaid/src/docs/config/accessibility.md @@ -4,83 +4,121 @@ Now with Mermaid library in much wider use, we have started to work towards more accessible features, based on the feedback from the community. -To begin with, we have added a new feature to Mermaid library, which is to support accessibility options, **Accessibility Title** and **Accessibility Description**. +Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). +[Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -This support for accessibility options is available for all the diagrams/chart types. Also, we have tired to keep the same format for the accessibility options, so that it is easy to understand and maintain. +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) -## Defining Accessibility Options +### aria-roledescription -### Single line accessibility values +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) -The diagram authors can now add the accessibility options in the diagram definition, using the `accTitle` and `accDescr` keywords, where each keyword is followed by `:` and the string value for title and description like: +For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ +```html + +``` -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` +### Accessible Title and Description -**When these two options are defined, they will add a corresponding `` and `` tag in the SVG.** +Support for accessible titles and descriptions is available for all diagrams/chart types. We have tried to keep the same keywords and format for all diagrams so that it is easy to understand and maintain. -Let us take a look at the following example with a flowchart diagram: +The accessible title and description will add `` and `<desc>` elements within the SVG element and the [aria-labelledby](https://www.w3.org/TR/wai-aria/#aria-labelledby) and [aria-describedby](https://www.w3.org/TR/wai-aria/#aria-describedby) attributes in the SVG tag. + +Here is HTML that is generated, showing that the SVG element is labelled by the accessible title (id = `chart-title-mermaid-1668725057758`) +and described by the accessible description (id = `chart-desc-mermaid-1668725057758` ); +and the accessible title element (text = "This is the accessible title") +and the accessible description element (text = "This is an accessible description"). + +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ + +```html +<svg aria-labelledby="chart-title-mermaid-1668725057758" aria-describedby="chart-desc-mermaid-1668725057758" xmlns="http://www.w3.org/2000/svg" width="100%" id="mermaid-1668725057758"> + <title id="chart-title-mermaid-1668725057758">This is the accessible title + This is an accessible description +``` + +Details for the syntax follow. + + +#### accessible title +The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. +The string value ends at the end of the line. (It can only be a single line.) + +Ex: `accTitle: This is a single line title` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + + +#### accessible description + +An accessible description can be 1 line long (a single line) or many lines long. + +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. + +Ex: `accDescr: This is a single line description.` + +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). + +Ex: +``` +accDescr { The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: see the big decision and then make the big decision.} +``` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + + +#### accTitle and accDescr Usage Examples + +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] +``` + +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ +```html + + Big decisions + Bob's Burgers process for making big decisions ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: - -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) - -### Multi-line Accessibility title/description - -You can also define the accessibility options in a multi-line format, where the keyword is followed by opening curly bracket `{` and then multiple lines, followed by a closing `}`. - -`accTitle: My single line title value` (**_single line format_**) - -vs - -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) - -Let us look at it in the following example, with same flowchart: +* Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision." ```mermaid-example - graph LR - accTitle: Big decisions - + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] +``` - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ +```html + + Big decisions + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) +#### Sample Code Snippets for other diagram types -### Sample Code Snippet for other diagram types - -#### Sequence Diagram - -```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description - - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! -``` - -#### Class Diagram +##### Class Diagram ```mermaid-example classDiagram @@ -90,18 +128,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the Vehicle <|-- Car ``` -#### State Diagram - -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -#### Entity Relationship Diagram +##### Entity Relationship Diagram ```mermaid-example erDiagram @@ -114,25 +141,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### User Journey Diagram - -```mermaid-example - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -#### Gantt Chart +##### Gantt Chart ```mermaid-example gantt @@ -150,7 +159,27 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Pie Chart +##### Gitgraph + +```mermaid-example + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +##### Pie Chart ```mermaid-example pie @@ -165,7 +194,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Requirement Diagram +##### Requirement Diagram ```mermaid-example requirementDiagram @@ -187,22 +216,44 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Gitgraph +##### Sequence Diagram ```mermaid-example - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +##### State Diagram + +```mermaid-example + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description + + s1 --> s2 + +``` + + +##### User Journey Diagram + +```mermaid-example + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me ``` From 9cc862b951082dbe143e7bc5fb3fc2b5875862af Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 15:48:17 -0800 Subject: [PATCH 13/27] doc: adding diagrams: revise a11y section --- .../mermaid/src/docs/community/newDiagram.md | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/packages/mermaid/src/docs/community/newDiagram.md b/packages/mermaid/src/docs/community/newDiagram.md index 74026b3ff..ed8c1e8aa 100644 --- a/packages/mermaid/src/docs/community/newDiagram.md +++ b/packages/mermaid/src/docs/community/newDiagram.md @@ -8,8 +8,8 @@ This would be to define a jison grammar for the new diagram type. That should st For instance: -- the flowchart starts with the keyword graph. -- the sequence diagram starts with the keyword sequenceDiagram +- the flowchart starts with the keyword _graph_ +- the sequence diagram starts with the keyword _sequenceDiagram_ #### Store data found during parsing @@ -56,6 +56,11 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. + +Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. ### Step 4: The final piece - triggering the rendering @@ -163,19 +168,23 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility -The syntax for adding title and description looks like this: +Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: +- aria-roledescription +- accessible title +- accessible description -``` -accTitle: The title -accDescr: The description -accDescr { - Syntax for a description text - written on multiple lines. -} -``` +### aria-roledescription -In a similar way to the directives the jison syntax are quite similar between the diagrams. +The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. + +See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) in [the Accessible Rich Internet Applications W3 standard.](https://www.w3.org/WAI/standards-guidelines/aria/) + +### accessible title and description + +The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) + +In a similar way to the directives, the jison syntax are quite similar between the diagrams. ```jison @@ -213,18 +222,8 @@ import { } from '../../commonDb'; ``` -For rendering the accessibility tags you have again an existing function you can use. +The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. -**In the renderer:** - -```js -import addSVGAccessibilityFields from '../../accessibility'; - -/* ... */ - -// Adds title and description to the flow chart -addSVGAccessibilityFields(parser.yy, svg, id); -``` ## Theming From 68b1805c40dc4505cc755d7cae475cbd2444cd83 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 15:49:37 -0800 Subject: [PATCH 14/27] (minor) fix typo, whitespace formatting --- demos/state.html | 4 ++-- packages/mermaid/src/mermaidAPI.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/state.html b/demos/state.html index dbe2286a3..6f6dfba00 100644 --- a/demos/state.html +++ b/demos/state.html @@ -68,8 +68,8 @@
     stateDiagram-v2
       accTitle: very very simple state
-    accDescr: This is a state diagram showing one state
-    State1
+      accDescr: This is a state diagram showing one state
+      State1
     

diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 18076b488..072ff595a 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -381,7 +381,7 @@ export const removeExistingElements = ( * @param id - The id for the SVG element (the element to be rendered) * @param text - The text for the graph definition * @param cb - Callback which is called after rendering is finished with the svg code as in param. - * @param container - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) + * @param svgContainingElement - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) * If no svgContainingElement is provided then the SVG element will be appended to the body. * Selector to element in which a div with the graph temporarily will be * inserted. If one is provided a hidden div will be inserted in the body of the page instead. The From b51759d36eebfdf7445909b048f0e8a0dfd540fd Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 15:50:52 -0800 Subject: [PATCH 15/27] set describeby to accessible description element id --- packages/mermaid/src/accessibility.spec.ts | 170 +++++++++++++-------- packages/mermaid/src/accessibility.ts | 7 +- 2 files changed, 112 insertions(+), 65 deletions(-) diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts index 7336284fe..87d9a1cd0 100644 --- a/packages/mermaid/src/accessibility.spec.ts +++ b/packages/mermaid/src/accessibility.spec.ts @@ -33,46 +33,95 @@ describe('accessibility', () => { expect(noInsert_attr_spy).not.toHaveBeenCalled(); }); + // ---------------- + // Convenience functions to DRY up the spec + + function expectAriaLabelledByIsTitleId( + svgD3Node: any, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + } + + function expectAriaDescribedByIsDescId( + svgD3Node: any, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svg_attr_spy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`); + } + + function a11yTitleTagInserted( + svgD3Node: any, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title); + } + + function a11yDescTagInserted( + svgD3Node: any, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc); + } + + function a11yTagInserted( + svgD3Node: any, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number, + expectedPrefix: string, + expectedText: string | null | undefined + ) { + const faux_insertedD3 = new MockedD3(); + const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_insertedD3); + // @ts-ignore Required to easily handle the d3 select types + const title_attr_spy = vi.spyOn(faux_insertedD3, 'attr').mockReturnValue(faux_insertedD3); + const title_text_spy = vi.spyOn(faux_insertedD3, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId); + expect(svg_insert_spy).toHaveBeenCalledWith(expectedPrefix, ':first-child'); + expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`); + expect(title_text_spy).toHaveBeenNthCalledWith(callNumber, expectedText); + } + // ---------------- + describe('given an a11y title', () => { const a11yTitle = 'a11y title'; describe('given an a11y description', () => { const a11yDesc = 'a11y description'; - it('sets aria-labelledby to the title id and the description id inserted as children', () => { - // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith( - 'aria-labelledby', - `chart-title-${givenId} chart-desc-${givenId}` - ); + it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); }); it('inserts a title tag as the first child with the text set to the accTitle given', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); - // @ts-ignore Required to easily handle the d3 select types - const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); - const title_text_spy = vi.spyOn(faux_title, 'text'); - - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); - expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); - expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2); }); it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { - const faux_desc = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); - // @ts-ignore Required to easily handle the d3 select types - const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); - const desc_text_spy = vi.spyOn(faux_desc, 'text'); - - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); - expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); - expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); }); }); @@ -80,23 +129,18 @@ describe('accessibility', () => { const a11yDesc = undefined; it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('no aria-describedby is set', () => { // @ts-ignore Required to easily handle the d3 select types const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); }); it('inserts a title tag as the first child with the text set to the accTitle given', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); - // @ts-ignore Required to easily handle the d3 select types - const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title); - const title_text_spy = vi.spyOn(faux_title, 'text'); - - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('title', ':first-child'); - expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-title-` + givenId); - expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y title'); + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); }); it('no description tag is inserted', () => { @@ -114,6 +158,13 @@ describe('accessibility', () => { describe('given an a11y description', () => { const a11yDesc = 'a11y description'; + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + it('no title tag inserted', () => { const faux_title = new MockedD3(); const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); @@ -121,30 +172,32 @@ describe('accessibility', () => { expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child'); }); - it('sets aria-labelledby to the description id inserted as a child', () => { - // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-desc-${givenId}`); + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); }); - it('inserts a desc tag as a child with the text set to accDescription given', () => { - const faux_desc = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); - // @ts-ignore Required to easily handle the d3 select types - const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc); - const desc_text_spy = vi.spyOn(faux_desc, 'text'); - - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child'); - expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId); - expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description'); + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); }); }); describe('no a11y description', () => { const a11yDesc = undefined; + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + + it('no aria-describedby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + }); + it('no title tag inserted', () => { const faux_title = new MockedD3(); const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); @@ -158,13 +211,6 @@ describe('accessibility', () => { addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child'); }); - - it('no aria-labelledby is set', () => { - // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); - addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).not.toHaveBeenCalled(); - }); }); }); }); diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts index eff9a4edc..2940de959 100644 --- a/packages/mermaid/src/accessibility.ts +++ b/packages/mermaid/src/accessibility.ts @@ -40,14 +40,15 @@ export function addSVGa11yTitleDescription( return; } - const titleId = a11yTitle ? 'chart-title-' + baseId : null; - const descId = a11yDesc ? 'chart-desc-' + baseId : null; if (a11yTitle || a11yDesc) { - svg.attr('aria-labelledby', compact([titleId, descId]).join(' ')); if (a11yDesc) { + const descId = 'chart-desc-' + baseId; + svg.attr('aria-describedby', descId); svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc); } if (a11yTitle) { + const titleId = 'chart-title-' + baseId; + svg.attr('aria-labelledby', titleId); svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle); } } else { From 69526402e2d952352e3195080fa7b2553ee2c767 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 16:51:23 -0800 Subject: [PATCH 16/27] format .md files --- docs/community/development.md | 246 +++++++++++++++--- .../mermaid/src/docs/community/newDiagram.md | 9 +- .../mermaid/src/docs/config/accessibility.md | 67 +++-- 3 files changed, 259 insertions(+), 63 deletions(-) diff --git a/docs/community/development.md b/docs/community/development.md index 58ca4670b..8503fb141 100644 --- a/docs/community/development.md +++ b/docs/community/development.md @@ -4,7 +4,17 @@ > > ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/development.md](../../packages/mermaid/src/docs/community/development.md). -# Development and Contribution 🙌 +# Contributing to Mermaid + +## Contents + +- [Technical Requirements and Setup](#technical-requirements-and-setup) +- [Contributing Code](#contributing-code) +- [Contributing Documentation](#contributing-documentation) +- [Questions or Suggestions?](#questions-or-suggestions) +- [Last Words](#last-words) + +--- So you want to help? That's great! @@ -12,72 +22,141 @@ So you want to help? That's great! Here are a few things to get you started on the right path. -**The Docs Structure is dictated by [.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. +## Technical Requirements and Setup -**Note: Commits and Pull Requests should be directed to the develop branch.** +### Technical Requirements -## Branching +These are the tools we use for working with the code and documentation. -Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching. So development is done in the `develop` branch. +- [volta](https://volta.sh/) to manage node versions. +- [Node.js](https://nodejs.org/en/). `volta install node` +- [pnpm](https://pnpm.io/) package manager. `volta install pnpm` +- [npx](https://docs.npmjs.com/cli/v8/commands/npx) the packaged executor in npm. This is needed [to install pnpm.](#2-install-pnpm) -Once development is done we branch a `release` branch from `develop` for testing. +Follow [the setup steps below](#setup) to install them and verify they are working -Once the release happens we merge the `release` branch with `master` and kill the `release` branch. +### Setup -This means that **you should branch off your pull request from develop** and direct all Pull Requests to it. +Follow these steps to set up the environment you need to work on code and/or documentation. + +#### 1. Fork and clone the repository + +In GitHub, you first _fork_ a repository when you are going to make changes and submit pull requests. + +Then you _clone_ a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with. + +[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo) + +#### 2. Install pnpm + +Once you have cloned the repository onto your development machine, change into the `mermaid` project folder so that you can install `pnpm`. You will need `npx` to install pnpm because volta doesn't support it yet. + +Ex: + +```bash +# Change into the mermaid directory (the top level director of the mermaid project repository) +cd mermaid +# npx is required for first install because volta does not support pnpm yet +npx pnpm install +``` + +#### 3. Verify Everything Is Working + +Once you have installed pnpm, you can run the `test` script to verify that pnpm is working _and_ that the repository has been cloned correctly: + +```bash +pnpm test +``` + +The `test` script and others are in the top-level `package.json` file. + +All tests should run sucessfully without any errors or failures. (You might see _lint_ or _formatting_ warnings; those are ok during this step.) ## Contributing Code -We make all changes via Pull Requests. As we have many Pull Requests from developers new to mermaid, we have put in place a process, wherein _knsv, Knut Sveidqvist_ is the primary reviewer of changes and merging pull requests. The process is as follows: +The basic steps for contributing code are: -- Large changes reviewed by knsv or other developer asked to review by knsv -- Smaller, low-risk changes like dependencies, documentation, etc. can be merged by active collaborators -- Documentation (we encourage updates to the `/packages/mermaid/src/docs` folder; you can submit them via direct commits) +```mermaid-example +graph LR + git[1. Checkout a git branch] --> codeTest[2. write tests and code] --> doc[3. update documentation] --> submit[4.submit a PR] +``` -When you commit code, create a branch with the following naming convention: +```mermaid +graph LR + git[1. Checkout a git branch] --> codeTest[2. write tests and code] --> doc[3. update documentation] --> submit[4.submit a PR] +``` -Start with the type, such as **feature** or **bug**, followed by the issue number for reference, and a text that describes the issue. +1. **Create** and checkout a git branch and work on your code in the branch +2. Write and update **tests** (unit and perhaps even integration (e2e) tests) (If you do TDD/BDD, the order might be different.) +3. **Let users know** that things have changed or been added in the documents! This is often overlooked, but _critical_ +4. **Submit** your code as a _pull request._ -**One example:** +### 1. Checkout a git branch -`feature/945_state_diagrams` +Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching. -**Another example:** +Development is done in the `develop` branch. -`bug/123_nasty_bug_branch` +Once development is done we branch a `release` branch from `develop` for testing. -## Contributing to Documentation +Once the release happens we merge the `release` branch with `master` and delete the `release` branch. The live product and on-line documentation are what is in the `master` branch. -If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature? +**All new work should be based on the `develop` branch.** -The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via **[.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. +**When you are ready to do work, always, ALWAYS:** -> **All the documents displayed in the GitHub.io page are listed in [.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. +1. Make sure you have the most up to date version of the `develop` branch. (fetch or pull to update it) +2. Check out the `develop` branch +3. Create a new branch for your work. Please name the branch following our naming convention below. -The contents of are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. +We use the follow naming convention for branches: -## How to Contribute to Documentation +```text + [feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces] +``` -We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator. +- The first part is the **type** of change: a feature, bug, chore, or documentation change ('docs') +- followed by a _slash_ (which helps to group like types together in many git tools) +- followed by the **issue number** +- followed by an _underscore_ ('\_') +- followed by a short text description (but use dashes ('-') or underscores ('\_') instead of spaces) -The documentation is located in the `src/docs` directory and organized according to relevant subfolder. +If your work is specific to a single diagram type, it is a good idea to put the diagram type at the start of the dscription. This will help use keep release notes organized: it will help us keep changes for a diagram type together. -The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually. +**Ex: A new feature described in issue 2945 that adds a new arrow type called 'florbs' to state diagrams** -We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s) +`feature/2945_state-diagram-new-arrow-florbs` -### Add Unit Tests for Parsing +**Ex: A bug described in issue 1123 that causes random ugly red text in multiple diagram types** +`bug/1123_fix_random_ugly_red_text` -This is important so that, if someone that does not know about this great feature suggests a change to the grammar, they get notified early on when that change breaks the parser. Another important aspect is that, without proper parsing, tests refactoring is pretty much impossible. +### 2. Write Tests -### Add E2E Tests +Tests ensure that each function, module, or part of code does what it says it will do. This is critically +important when other changes are made to ensure that existing code is not broken (no regression). -This tests the rendering and visual appearance of the diagrams. This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks! +Just as important, the tests act as _specifications:_ they specify what the code does (or should do). +Whenever someone is new to a section of code, they should be able to read the tests to get a thorough understanding of what it does and why. + +If you are fixing a bug, you should add tests to ensure that your code has actually fixed the bug, to specify/describe what the code is doing, and to ensure the bug doesn't happen again. +(If there had been a test for the situation, the bug never would have happened in the first place.) +You may need to change existing tests if they were inaccurate. + +If you are adding a feature, you will definitely need to add tests. Depending on the size of your feature, you may need to add integration tests. + +#### Unit Tests for Parsing + +If you are adding or changing the text that describes a diagram (the _grammar_), you will need to add (or change) tests for the _parser._ + +#### Integration/End-to-End (e2e) tests + +These test the rendering and visual appearance of the diagrams. +This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks! To start working with the e2e tests: -1. Run `pnpm run dev` to start the dev server -2. Start **Cypress** by running `pnpm exec cypress open` in the **mermaid** folder. +1. Run `pnpm run dev` to start the dev server (or use the `pnpm dev` script) +2. Start **Cypress** by running `pnpm exec cypress open` in the **mermaid** folder (or use the `pnpm cypress:open` script). The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress. @@ -107,17 +186,87 @@ it('should render forks and joins', () => { }); ``` -### Any Questions or Suggestions? +\[TODO - running the tests against what is expected in development. ] -After logging in at [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22). +\[TODO - how to generate new screenshots] +.... -### How to Contribute a Suggestion +### 3. Update Documentation + +If the users have no way to know that things have changed, then you haven't really _fixed_ anything for the users; you've just added to making Mermaid feel broken. +Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused. + +The documentation has to be updated to users know that things have changed and added! + +We know it can sometimes be hard to code _and_ write user documentation. + +\[TODO - how to submit documentation changes -- see [Contributing Documentation](#contributing-documentation) + +Create another issue specifically for the documentation.\ +You will need to help with the PR, but definitely ask for help if you feel stuck. +When it feels hard to write stuff out, explaining it to someone and having that person ask you clarifying questions can often be 80% of the work!] + +When in doubt, write up and submit what you can. It can be clarified and refined later. (With documentation, something is better than nothing!) + +### 4. Submit your pull request + +\[TODO - PR titles should start with (fix | feat | ....)] + +We make all changes via Pull Requests (PRs). As we have many Pull Requests from developers new to Mermaid, \ +we have put in place a process wherein _knsv, Knut Sveidqvist_ is the primary reviewer of changes and merging pull requests. The process is as follows: + +- Large changes are reviewed by knsv or other developer asked to review by knsv +- Smaller, low-risk changes like dependencies, documentation, etc. can be reviewed and merged by active collaborators + +**Reminder: Pull Requests should be submitted to the develop branch.** + +## Contributing Documentation + +\[TODO: This section is still a WIP. It still needs revision.] + +If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature? + +The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. +If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**. + +> **All the documents displayed in the GitHub.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**. + +The contents of are based on the docs from the `master` branch. +Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. + +### How to Contribute to Documentation + +We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator. + +The documentation is located in the `src/docs` directory and organized according to relevant subfolder. + +The contents of are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. + +**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)** + +The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually. + +We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s) + +- Documentation (we encourage updates to the `src/docs` folder; you can submit them via direct commits) + +The source files for documentation are in `/packages/mermaid/docs` and are written in markdown. + +**_DO NOT CHANGE FILES IN `/docs`_** + +### The official documentation site + +**[The mermaid documentation site](https://mermaid-js.github.io/mermaid/) is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.** + +\[TODO - how to preview the documents on a local machine? how to run VitePress?] + +If you want to preview the whole documentation site on your machine, you need to install `docsify-cli`: Markdown is used to format the text, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax). To edit Docs on your computer: -1. Find the Markdown file (.md) to edit in the [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs) directory in the `develop` branch. +1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs) directory in the `develop` branch. 2. Create a fork of the develop branch. 3. Make changes or add new documentation. 4. Commit changes to your fork and push it to GitHub. @@ -126,12 +275,31 @@ To edit Docs on your computer: To edit Docs on GitHub: 1. Login to [GitHub.com](https://www.github.com). -2. Navigate to [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs). +2. Navigate to [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). 3. To edit a file, click the pencil icon at the top-right of the file contents panel. 4. Describe what you changed in the **Propose file change** section, located at the bottom of the page. 5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch). 6. Create a Pull Request of your newly forked branch by clicking the green **Create Pull Request** button. +## Questions or Suggestions? + +#### First search to see if someone has already asked (and hopefully been answered) or suggested the same thing. + +- search in the Discussions +- search in the open Issues + +If you find an open issue or discussion thread that is similar to your question but isn't answered, +you can let us know that you are also interested in it. \[TODO: describe +1, upvote] +This helps the team know the relative interest in something and helps them set priorities and assignments. + +Feel free to add to the discussion on the issue or topic. + +If you can't find anything that already addresses your question or suggestion, _open a new issue:_ + +Log in to [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22). + +### How to Contribute a Suggestion + ## Last Words Don't get daunted if it is hard in the beginning. We have a great community with only encouraging words. So, if you get stuck, ask for help and hints in the Slack forum. If you want to show off something good, show it off there. diff --git a/packages/mermaid/src/docs/community/newDiagram.md b/packages/mermaid/src/docs/community/newDiagram.md index ed8c1e8aa..57a454671 100644 --- a/packages/mermaid/src/docs/community/newDiagram.md +++ b/packages/mermaid/src/docs/community/newDiagram.md @@ -56,9 +56,9 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. -[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader -would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. @@ -169,11 +169,11 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: -- aria-roledescription + +- aria-roledescription - accessible title - accessible description - ### aria-roledescription The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. @@ -224,7 +224,6 @@ import { The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. - ## Theming Mermaid supports themes and has an integrated theming engine. You can read more about how the themes can be used [in the docs](../config/theming.md). diff --git a/packages/mermaid/src/docs/config/accessibility.md b/packages/mermaid/src/docs/config/accessibility.md index 5545ce1bc..e7947adec 100644 --- a/packages/mermaid/src/docs/config/accessibility.md +++ b/packages/mermaid/src/docs/config/accessibility.md @@ -7,15 +7,22 @@ Now with Mermaid library in much wider use, we have started to work towards more Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). [Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) ### aria-roledescription -The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + ```html - + ``` ### Accessible Title and Description @@ -32,23 +39,29 @@ and the accessible description element (text = "This is an accessible descriptio _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ ```html - + This is the accessible title This is an accessible description + ``` Details for the syntax follow. - #### accessible title + The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. The string value ends at the end of the line. (It can only be a single line.) - -Ex: `accTitle: This is a single line title` + +Ex: `accTitle: This is a single line title` See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. - #### accessible description An accessible description can be 1 line long (a single line) or many lines long. @@ -60,6 +73,7 @@ Ex: `accDescr: This is a single line description.` A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). Ex: + ``` accDescr { The official Bob's Burgers corporate processes that are used for making very, very big decisions. @@ -68,7 +82,6 @@ accDescr { The official Bob's Burgers corporate processes that are used See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. - #### accTitle and accDescr Usage Examples - Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" @@ -82,14 +95,22 @@ See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-exam ``` Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + ```html - + Big decisions Bob's Burgers process for making big decisions - + ``` -* Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used +- Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used for making very, very big decisions. This is actually a very simple flow: identify the big decision and then make the big decision." @@ -106,16 +127,25 @@ Here is the HTML generated for the SVG element: _(Note that some of the SVG attr ``` Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + ```html - + Big decisions - The official Bob's Burgers corporate processes that are used - for making very, very big decisions. - This is actually a very simple flow: identify the big decision and then make the big decision. - + + The official Bob's Burgers corporate processes that are used for making very, very big + decisions. This is actually a very simple flow: identify the big decision and then make the big + decision. + + ``` - #### Sample Code Snippets for other diagram types ##### Class Diagram @@ -239,7 +269,6 @@ Here is the HTML generated for the SVG element: _(Note that some of the SVG attr ``` - ##### User Journey Diagram ```mermaid-example From 67a015c71d42e8cd6870337807244ff9e528d164 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 17 Nov 2022 16:58:18 -0800 Subject: [PATCH 17/27] re-re-fixed the contributing doc --- docs/community/development.md | 256 ++++++---------------------------- 1 file changed, 44 insertions(+), 212 deletions(-) diff --git a/docs/community/development.md b/docs/community/development.md index 8503fb141..58ca4670b 100644 --- a/docs/community/development.md +++ b/docs/community/development.md @@ -4,17 +4,7 @@ > > ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/development.md](../../packages/mermaid/src/docs/community/development.md). -# Contributing to Mermaid - -## Contents - -- [Technical Requirements and Setup](#technical-requirements-and-setup) -- [Contributing Code](#contributing-code) -- [Contributing Documentation](#contributing-documentation) -- [Questions or Suggestions?](#questions-or-suggestions) -- [Last Words](#last-words) - ---- +# Development and Contribution 🙌 So you want to help? That's great! @@ -22,141 +12,72 @@ So you want to help? That's great! Here are a few things to get you started on the right path. -## Technical Requirements and Setup +**The Docs Structure is dictated by [.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. -### Technical Requirements +**Note: Commits and Pull Requests should be directed to the develop branch.** -These are the tools we use for working with the code and documentation. +## Branching -- [volta](https://volta.sh/) to manage node versions. -- [Node.js](https://nodejs.org/en/). `volta install node` -- [pnpm](https://pnpm.io/) package manager. `volta install pnpm` -- [npx](https://docs.npmjs.com/cli/v8/commands/npx) the packaged executor in npm. This is needed [to install pnpm.](#2-install-pnpm) - -Follow [the setup steps below](#setup) to install them and verify they are working - -### Setup - -Follow these steps to set up the environment you need to work on code and/or documentation. - -#### 1. Fork and clone the repository - -In GitHub, you first _fork_ a repository when you are going to make changes and submit pull requests. - -Then you _clone_ a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with. - -[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo) - -#### 2. Install pnpm - -Once you have cloned the repository onto your development machine, change into the `mermaid` project folder so that you can install `pnpm`. You will need `npx` to install pnpm because volta doesn't support it yet. - -Ex: - -```bash -# Change into the mermaid directory (the top level director of the mermaid project repository) -cd mermaid -# npx is required for first install because volta does not support pnpm yet -npx pnpm install -``` - -#### 3. Verify Everything Is Working - -Once you have installed pnpm, you can run the `test` script to verify that pnpm is working _and_ that the repository has been cloned correctly: - -```bash -pnpm test -``` - -The `test` script and others are in the top-level `package.json` file. - -All tests should run sucessfully without any errors or failures. (You might see _lint_ or _formatting_ warnings; those are ok during this step.) - -## Contributing Code - -The basic steps for contributing code are: - -```mermaid-example -graph LR - git[1. Checkout a git branch] --> codeTest[2. write tests and code] --> doc[3. update documentation] --> submit[4.submit a PR] -``` - -```mermaid -graph LR - git[1. Checkout a git branch] --> codeTest[2. write tests and code] --> doc[3. update documentation] --> submit[4.submit a PR] -``` - -1. **Create** and checkout a git branch and work on your code in the branch -2. Write and update **tests** (unit and perhaps even integration (e2e) tests) (If you do TDD/BDD, the order might be different.) -3. **Let users know** that things have changed or been added in the documents! This is often overlooked, but _critical_ -4. **Submit** your code as a _pull request._ - -### 1. Checkout a git branch - -Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching. - -Development is done in the `develop` branch. +Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching. So development is done in the `develop` branch. Once development is done we branch a `release` branch from `develop` for testing. -Once the release happens we merge the `release` branch with `master` and delete the `release` branch. The live product and on-line documentation are what is in the `master` branch. +Once the release happens we merge the `release` branch with `master` and kill the `release` branch. -**All new work should be based on the `develop` branch.** +This means that **you should branch off your pull request from develop** and direct all Pull Requests to it. -**When you are ready to do work, always, ALWAYS:** +## Contributing Code -1. Make sure you have the most up to date version of the `develop` branch. (fetch or pull to update it) -2. Check out the `develop` branch -3. Create a new branch for your work. Please name the branch following our naming convention below. +We make all changes via Pull Requests. As we have many Pull Requests from developers new to mermaid, we have put in place a process, wherein _knsv, Knut Sveidqvist_ is the primary reviewer of changes and merging pull requests. The process is as follows: -We use the follow naming convention for branches: +- Large changes reviewed by knsv or other developer asked to review by knsv +- Smaller, low-risk changes like dependencies, documentation, etc. can be merged by active collaborators +- Documentation (we encourage updates to the `/packages/mermaid/src/docs` folder; you can submit them via direct commits) -```text - [feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces] -``` +When you commit code, create a branch with the following naming convention: -- The first part is the **type** of change: a feature, bug, chore, or documentation change ('docs') -- followed by a _slash_ (which helps to group like types together in many git tools) -- followed by the **issue number** -- followed by an _underscore_ ('\_') -- followed by a short text description (but use dashes ('-') or underscores ('\_') instead of spaces) +Start with the type, such as **feature** or **bug**, followed by the issue number for reference, and a text that describes the issue. -If your work is specific to a single diagram type, it is a good idea to put the diagram type at the start of the dscription. This will help use keep release notes organized: it will help us keep changes for a diagram type together. +**One example:** -**Ex: A new feature described in issue 2945 that adds a new arrow type called 'florbs' to state diagrams** +`feature/945_state_diagrams` -`feature/2945_state-diagram-new-arrow-florbs` +**Another example:** -**Ex: A bug described in issue 1123 that causes random ugly red text in multiple diagram types** -`bug/1123_fix_random_ugly_red_text` +`bug/123_nasty_bug_branch` -### 2. Write Tests +## Contributing to Documentation -Tests ensure that each function, module, or part of code does what it says it will do. This is critically -important when other changes are made to ensure that existing code is not broken (no regression). +If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature? -Just as important, the tests act as _specifications:_ they specify what the code does (or should do). -Whenever someone is new to a section of code, they should be able to read the tests to get a thorough understanding of what it does and why. +The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via **[.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. -If you are fixing a bug, you should add tests to ensure that your code has actually fixed the bug, to specify/describe what the code is doing, and to ensure the bug doesn't happen again. -(If there had been a test for the situation, the bug never would have happened in the first place.) -You may need to change existing tests if they were inaccurate. +> **All the documents displayed in the GitHub.io page are listed in [.vitepress/config.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/.vitepress/config.ts)**. -If you are adding a feature, you will definitely need to add tests. Depending on the size of your feature, you may need to add integration tests. +The contents of are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. -#### Unit Tests for Parsing +## How to Contribute to Documentation -If you are adding or changing the text that describes a diagram (the _grammar_), you will need to add (or change) tests for the _parser._ +We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator. -#### Integration/End-to-End (e2e) tests +The documentation is located in the `src/docs` directory and organized according to relevant subfolder. -These test the rendering and visual appearance of the diagrams. -This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks! +The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually. + +We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s) + +### Add Unit Tests for Parsing + +This is important so that, if someone that does not know about this great feature suggests a change to the grammar, they get notified early on when that change breaks the parser. Another important aspect is that, without proper parsing, tests refactoring is pretty much impossible. + +### Add E2E Tests + +This tests the rendering and visual appearance of the diagrams. This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks! To start working with the e2e tests: -1. Run `pnpm run dev` to start the dev server (or use the `pnpm dev` script) -2. Start **Cypress** by running `pnpm exec cypress open` in the **mermaid** folder (or use the `pnpm cypress:open` script). +1. Run `pnpm run dev` to start the dev server +2. Start **Cypress** by running `pnpm exec cypress open` in the **mermaid** folder. The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress. @@ -186,87 +107,17 @@ it('should render forks and joins', () => { }); ``` -\[TODO - running the tests against what is expected in development. ] +### Any Questions or Suggestions? -\[TODO - how to generate new screenshots] -.... +After logging in at [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22). -### 3. Update Documentation - -If the users have no way to know that things have changed, then you haven't really _fixed_ anything for the users; you've just added to making Mermaid feel broken. -Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused. - -The documentation has to be updated to users know that things have changed and added! - -We know it can sometimes be hard to code _and_ write user documentation. - -\[TODO - how to submit documentation changes -- see [Contributing Documentation](#contributing-documentation) - -Create another issue specifically for the documentation.\ -You will need to help with the PR, but definitely ask for help if you feel stuck. -When it feels hard to write stuff out, explaining it to someone and having that person ask you clarifying questions can often be 80% of the work!] - -When in doubt, write up and submit what you can. It can be clarified and refined later. (With documentation, something is better than nothing!) - -### 4. Submit your pull request - -\[TODO - PR titles should start with (fix | feat | ....)] - -We make all changes via Pull Requests (PRs). As we have many Pull Requests from developers new to Mermaid, \ -we have put in place a process wherein _knsv, Knut Sveidqvist_ is the primary reviewer of changes and merging pull requests. The process is as follows: - -- Large changes are reviewed by knsv or other developer asked to review by knsv -- Smaller, low-risk changes like dependencies, documentation, etc. can be reviewed and merged by active collaborators - -**Reminder: Pull Requests should be submitted to the develop branch.** - -## Contributing Documentation - -\[TODO: This section is still a WIP. It still needs revision.] - -If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature? - -The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. -If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**. - -> **All the documents displayed in the GitHub.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**. - -The contents of are based on the docs from the `master` branch. -Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. - -### How to Contribute to Documentation - -We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator. - -The documentation is located in the `src/docs` directory and organized according to relevant subfolder. - -The contents of are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released. - -**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)** - -The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually. - -We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s) - -- Documentation (we encourage updates to the `src/docs` folder; you can submit them via direct commits) - -The source files for documentation are in `/packages/mermaid/docs` and are written in markdown. - -**_DO NOT CHANGE FILES IN `/docs`_** - -### The official documentation site - -**[The mermaid documentation site](https://mermaid-js.github.io/mermaid/) is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.** - -\[TODO - how to preview the documents on a local machine? how to run VitePress?] - -If you want to preview the whole documentation site on your machine, you need to install `docsify-cli`: +### How to Contribute a Suggestion Markdown is used to format the text, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax). To edit Docs on your computer: -1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs) directory in the `develop` branch. +1. Find the Markdown file (.md) to edit in the [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs) directory in the `develop` branch. 2. Create a fork of the develop branch. 3. Make changes or add new documentation. 4. Commit changes to your fork and push it to GitHub. @@ -275,31 +126,12 @@ To edit Docs on your computer: To edit Docs on GitHub: 1. Login to [GitHub.com](https://www.github.com). -2. Navigate to [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). +2. Navigate to [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs). 3. To edit a file, click the pencil icon at the top-right of the file contents panel. 4. Describe what you changed in the **Propose file change** section, located at the bottom of the page. 5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch). 6. Create a Pull Request of your newly forked branch by clicking the green **Create Pull Request** button. -## Questions or Suggestions? - -#### First search to see if someone has already asked (and hopefully been answered) or suggested the same thing. - -- search in the Discussions -- search in the open Issues - -If you find an open issue or discussion thread that is similar to your question but isn't answered, -you can let us know that you are also interested in it. \[TODO: describe +1, upvote] -This helps the team know the relative interest in something and helps them set priorities and assignments. - -Feel free to add to the discussion on the issue or topic. - -If you can't find anything that already addresses your question or suggestion, _open a new issue:_ - -Log in to [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22). - -### How to Contribute a Suggestion - ## Last Words Don't get daunted if it is hard in the beginning. We have a great community with only encouraging words. So, if you get stuck, ask for help and hints in the Slack forum. If you want to show off something good, show it off there. From 2a98791ec9005d0ec0c8f2e0a71f1452ea7c993e Mon Sep 17 00:00:00 2001 From: Ashley Engelund Date: Sun, 20 Nov 2022 12:17:21 -0800 Subject: [PATCH 18/27] use optional chaining check for get acc title and get acc description Co-authored-by: Alois Klink --- packages/mermaid/src/mermaidAPI.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 072ff595a..c1b2f2556 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -529,8 +529,8 @@ const render = function ( // 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; + const a11yTitle = diag.db.getAccTitle?.(); + const a11yDescr = diag.db.getAccDescription?.(); addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id')); // ------------------------------------------------------------------------------- From a9c337302a5204dd3413659aa9ebacf34ecf779c Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 20 Nov 2022 12:27:29 -0800 Subject: [PATCH 19/27] export D3Element from mermaidAPI; use in accessibility --- docs/config/setup/modules/mermaidAPI.md | 10 ++++++++++ packages/mermaid/src/accessibility.spec.ts | 11 ++++++----- packages/mermaid/src/accessibility.ts | 10 ++++------ packages/mermaid/src/mermaidAPI.ts | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index baa4a939c..ef8a08b91 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -12,6 +12,16 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) +## Type Aliases + +### D3Element + +Ƭ **D3Element**: `any` + +#### Defined in + +[mermaidAPI.ts:73](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L73) + ## Variables ### mermaidAPI diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts index 87d9a1cd0..57d5e8933 100644 --- a/packages/mermaid/src/accessibility.spec.ts +++ b/packages/mermaid/src/accessibility.spec.ts @@ -1,5 +1,6 @@ import { MockedD3 } from './tests/MockedD3'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; +import { D3Element } from './mermaidAPI'; describe('accessibility', () => { const fauxSvgNode = new MockedD3(); @@ -37,7 +38,7 @@ describe('accessibility', () => { // Convenience functions to DRY up the spec function expectAriaLabelledByIsTitleId( - svgD3Node: any, + svgD3Node: D3Element, title: string | null | undefined, desc: string | null | undefined, givenId: string @@ -49,7 +50,7 @@ describe('accessibility', () => { } function expectAriaDescribedByIsDescId( - svgD3Node: any, + svgD3Node: D3Element, title: string | null | undefined, desc: string | null | undefined, givenId: string @@ -61,7 +62,7 @@ describe('accessibility', () => { } function a11yTitleTagInserted( - svgD3Node: any, + svgD3Node: D3Element, title: string | null | undefined, desc: string | null | undefined, givenId: string, @@ -71,7 +72,7 @@ describe('accessibility', () => { } function a11yDescTagInserted( - svgD3Node: any, + svgD3Node: D3Element, title: string | null | undefined, desc: string | null | undefined, givenId: string, @@ -81,7 +82,7 @@ describe('accessibility', () => { } function a11yTagInserted( - svgD3Node: any, + svgD3Node: D3Element, title: string | null | undefined, desc: string | null | undefined, givenId: string, diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts index 2940de959..a84edec0c 100644 --- a/packages/mermaid/src/accessibility.ts +++ b/packages/mermaid/src/accessibility.ts @@ -2,11 +2,9 @@ * Accessibility (a11y) functions, types, helpers * */ +import { D3Element } from './mermaidAPI'; -import { isEmpty, compact } from 'lodash'; - -// This is just a convenience alias to make it clear the type is a d3 object. (It's easier to make it 'any' instead of the complete typing set in d3) -type D3object = any; +import { isEmpty } from 'lodash'; /** * Add aria-roledescription to the svg element to the diagramType @@ -14,7 +12,7 @@ type D3object = any; * @param svg - d3 object that contains the SVG HTML element * @param diagramType - diagram name for to the aria-roledescription */ -export function setA11yDiagramInfo(svg: D3object, diagramType: string | null | undefined) { +export function setA11yDiagramInfo(svg: D3Element, diagramType: string | null | undefined) { if (!isEmpty(diagramType)) { svg.attr('aria-roledescription', diagramType); } @@ -31,7 +29,7 @@ export function setA11yDiagramInfo(svg: D3object, diagramType: string | null | u * @param baseId - id used to construct the a11y title and description id */ export function addSVGa11yTitleDescription( - svg: D3object, + svg: D3Element, a11yTitle: string | null | undefined, a11yDesc: string | null | undefined, baseId: string diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index c1b2f2556..808dec809 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -70,7 +70,7 @@ interface DiagramStyleClassDef { // This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type. // @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files. -type D3Element = any; +export type D3Element = any; // ---------------------------------------------------------------------------- From 7508cd796d8e6b50e64503d429031e0601c9659e Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 10:13:32 -0800 Subject: [PATCH 20/27] (minor) fix comment, comment typo --- packages/mermaid/src/tests/MockedD3.ts | 2 +- packages/mermaid/src/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index 24cb9043f..56787448a 100644 --- a/packages/mermaid/src/tests/MockedD3.ts +++ b/packages/mermaid/src/tests/MockedD3.ts @@ -99,7 +99,7 @@ export class MockedD3 { return this; } - // NOTE: Returns a HTML ELement with tag 'svg' that has _another_ 'svg' element child. + // NOTE: Returns a HTML Element with tag 'svg' that has _another_ 'svg' element child. // This allows different tests to succeed -- some need a top level 'svg' and some need a 'svg' element to be the firstChild // Real implementation returns an HTML Element public node = vi.fn().mockImplementation(() => { diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 3689a01a1..19ed228f9 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -915,7 +915,7 @@ export function getErrorMessage(error: unknown): string { } /** - * Appends element with the given title, centered. + * Appends element with the given title and css class. * * @param parent - d3 svg object to append title to * @param cssClass - CSS class for the element containing the title From f1bc2deafd1f8ec30ccea37d853fb944480fbed6 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 10:14:11 -0800 Subject: [PATCH 21/27] use MockedD3, spies in util insertTitle spec (remove MockD3) --- packages/mermaid/src/utils.spec.js | 60 +++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 54262f10e..769a0d0cc 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -4,7 +4,8 @@ import assignWithDepth from './assignWithDepth'; import { detectType } from './diagram-api/detectType'; import { addDiagrams } from './diagram-api/diagram-orchestration'; import memoize from 'lodash-es/memoize'; -import { MockD3 } from 'd3'; +import { MockedD3 } from './tests/MockedD3'; + addDiagrams(); describe('when assignWithDepth: should merge objects within objects', function () { @@ -352,21 +353,52 @@ describe('when initializing the id generator', function () { }); describe('when inserting titles', function () { - it('should do nothing when title is empty', function () { - const svg = MockD3('svg'); - utils.insertTitle(svg, 'testClass', 0, ''); - expect(svg.__children.length).toBe(0); + const svg = new MockedD3('svg'); + const mockedElement = { + getBBox: vi.fn().mockReturnValue({ x: 10, y: 11, width: 100, height: 200 }), + }; + const fauxTitle = new MockedD3('title'); + + beforeEach(() => { + svg.node = vi.fn().mockReturnValue(mockedElement); }); - it('should insert title centered', function () { - const svg = MockD3('svg'); + it('does nothing if the title is empty', function () { + const svg_append_spy = vi.spyOn(svg, 'append'); + utils.insertTitle(svg, 'testClass', 0, ''); + expect(svg_append_spy).not.toHaveBeenCalled(); + }); + + it('appends the title as a text item with the given title text', function () { + const svg_append_spy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const title_text_spy = vi.spyOn(fauxTitle, 'text'); + utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(svg.__children.length).toBe(1); - const text = svg.__children[0]; - expect(text.__name).toBe('text'); - expect(text.text).toHaveBeenCalledWith('test title'); - expect(text.attr).toHaveBeenCalledWith('x', 15); - expect(text.attr).toHaveBeenCalledWith('y', -5); - expect(text.attr).toHaveBeenCalledWith('class', 'testClass'); + expect(svg_append_spy).toHaveBeenCalled(); + expect(title_text_spy).toHaveBeenCalledWith('test title'); + }); + + it('x value is the bounds x position + half of the bounds width', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(title_attr_spy).toHaveBeenCalledWith('x', 10 + 100 / 2); + }); + + it('y value is - given title top margin', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(title_attr_spy).toHaveBeenCalledWith('y', -5); + }); + + it('class is the given css class', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(title_attr_spy).toHaveBeenCalledWith('class', 'testClass'); }); }); From 6e486d3c49fe3aa8a8480cf486f07a53838673ed Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 10:32:25 -0800 Subject: [PATCH 22/27] add test for multi-line accDescr --- .../src/diagrams/er/parser/erDiagram.spec.js | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index 6131f7697..43bc13e6d 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -323,18 +323,34 @@ describe('when parsing ER diagram it...', function () { expect(Object.keys(erDb.getEntities()).length).toBe(1); }); - it('should allow for a accessibility title and description (accDescr)', function () { + describe('accessible title and description', () => { const teacherRole = 'is teacher of'; const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; - erDiagram.parser.parse( - `erDiagram + it('should allow for a accessibility title and description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram accTitle: graph title accDescr: this graph is about stuff ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + ); + expect(erDb.getAccTitle()).toBe('graph title'); + expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + }); + + it('parses a multi line description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram + accTitle: graph title + accDescr { this graph is + about + stuff + }\n + ${line1}` + ); + expect(erDb.getAccTitle()).toEqual('graph title'); + expect(erDb.getAccDescription()).toEqual('this graph is\nabout\nstuff'); + }); }); it('should allow more than one relationship between the same two entities', function () { From 2030885fd3307829cee44a1c3187311e2dd5cae1 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 10:46:43 -0800 Subject: [PATCH 23/27] update /docs --- docs/config/setup/modules/mermaidAPI.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index ef8a08b91..e236538e0 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -20,7 +20,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:73](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L73) +[mermaidAPI.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L72) ## Variables @@ -90,7 +90,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:960](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L960) +[mermaidAPI.ts:959](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L959) ## Functions @@ -121,7 +121,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:294](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L294) +[mermaidAPI.ts:293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L293) --- @@ -147,7 +147,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:245](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L245) +[mermaidAPI.ts:244](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L244) --- @@ -173,7 +173,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L172) +[mermaidAPI.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L171) --- @@ -196,7 +196,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:222](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L222) +[mermaidAPI.ts:221](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L221) --- @@ -223,7 +223,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156) +[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) --- @@ -243,7 +243,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L130) +[mermaidAPI.ts:129](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L129) --- @@ -263,7 +263,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L101) +[mermaidAPI.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L100) --- @@ -289,7 +289,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:273](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L273) +[mermaidAPI.ts:272](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L272) --- @@ -315,4 +315,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:345](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L345) +[mermaidAPI.ts:344](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L344) From 6044e9e9e820e1412515241b81e267091d43289c Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 19:17:17 -0800 Subject: [PATCH 24/27] make test title clearer --- packages/mermaid/src/utils.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 769a0d0cc..1c40cd6cc 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -386,7 +386,7 @@ describe('when inserting titles', function () { expect(title_attr_spy).toHaveBeenCalledWith('x', 10 + 100 / 2); }); - it('y value is - given title top margin', () => { + it('y value is the negative of given title top margin', () => { vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); From 2bf753a769ed816dab2a2ea81860ff1d02934134 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Sun, 27 Nov 2022 19:17:37 -0800 Subject: [PATCH 25/27] use camelCase --- packages/mermaid/src/accessibility.spec.ts | 74 +++++++++++----------- packages/mermaid/src/utils.spec.js | 24 +++---- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts index 57d5e8933..c633d0e15 100644 --- a/packages/mermaid/src/accessibility.spec.ts +++ b/packages/mermaid/src/accessibility.spec.ts @@ -8,16 +8,16 @@ describe('accessibility', () => { describe('setA11yDiagramInfo', () => { it('sets the aria-roledescription to the diagram type', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); setA11yDiagramInfo(fauxSvgNode, 'flowchart'); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); }); it('does nothing if the diagram type is empty', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); setA11yDiagramInfo(fauxSvgNode, ''); - expect(svg_attr_spy).not.toHaveBeenCalled(); + expect(svgAttrSpy).not.toHaveBeenCalled(); }); }); @@ -29,9 +29,9 @@ describe('accessibility', () => { const noInsertSvg = { attr: vi.fn(), }; - const noInsert_attr_spy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); + const noInsertAttrSpy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId); - expect(noInsert_attr_spy).not.toHaveBeenCalled(); + expect(noInsertAttrSpy).not.toHaveBeenCalled(); }); // ---------------- @@ -44,9 +44,9 @@ describe('accessibility', () => { givenId: string ) { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); } function expectAriaDescribedByIsDescId( @@ -56,9 +56,9 @@ describe('accessibility', () => { givenId: string ) { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); - expect(svg_attr_spy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`); } function a11yTitleTagInserted( @@ -90,16 +90,16 @@ describe('accessibility', () => { expectedPrefix: string, expectedText: string | null | undefined ) { - const faux_insertedD3 = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_insertedD3); + const fauxInsertedD3 = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3); // @ts-ignore Required to easily handle the d3 select types - const title_attr_spy = vi.spyOn(faux_insertedD3, 'attr').mockReturnValue(faux_insertedD3); - const title_text_spy = vi.spyOn(faux_insertedD3, 'text'); + const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3); + const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text'); addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId); - expect(svg_insert_spy).toHaveBeenCalledWith(expectedPrefix, ':first-child'); - expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`); - expect(title_text_spy).toHaveBeenNthCalledWith(callNumber, expectedText); + expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child'); + expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`); + expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText); } // ---------------- @@ -135,9 +135,9 @@ describe('accessibility', () => { it('no aria-describedby is set', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); }); it('inserts a title tag as the first child with the text set to the accTitle given', () => { @@ -145,10 +145,10 @@ describe('accessibility', () => { }); it('no description tag is inserted', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child'); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); }); }); }); @@ -161,16 +161,16 @@ describe('accessibility', () => { it('no aria-labelledby is set', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); }); it('no title tag inserted', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child'); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); }); it('sets aria-describedby to the description id inserted as a child', () => { @@ -187,30 +187,30 @@ describe('accessibility', () => { it('no aria-labelledby is set', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); }); it('no aria-describedby is set', () => { // @ts-ignore Required to easily handle the d3 select types - const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); }); it('no title tag inserted', () => { - const faux_title = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title); + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child'); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); }); it('no description tag inserted', () => { - const faux_desc = new MockedD3(); - const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc); + const fauxDesc = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc); addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); - expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child'); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); }); }); }); diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 1c40cd6cc..e983d21c8 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -364,41 +364,41 @@ describe('when inserting titles', function () { }); it('does nothing if the title is empty', function () { - const svg_append_spy = vi.spyOn(svg, 'append'); + const svgAppendSpy = vi.spyOn(svg, 'append'); utils.insertTitle(svg, 'testClass', 0, ''); - expect(svg_append_spy).not.toHaveBeenCalled(); + expect(svgAppendSpy).not.toHaveBeenCalled(); }); it('appends the title as a text item with the given title text', function () { - const svg_append_spy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); - const title_text_spy = vi.spyOn(fauxTitle, 'text'); + const svgAppendSpy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleTextSpy = vi.spyOn(fauxTitle, 'text'); utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(svg_append_spy).toHaveBeenCalled(); - expect(title_text_spy).toHaveBeenCalledWith('test title'); + expect(svgAppendSpy).toHaveBeenCalled(); + expect(titleTextSpy).toHaveBeenCalledWith('test title'); }); it('x value is the bounds x position + half of the bounds width', () => { vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); - const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(title_attr_spy).toHaveBeenCalledWith('x', 10 + 100 / 2); + expect(titleAttrSpy).toHaveBeenCalledWith('x', 10 + 100 / 2); }); it('y value is the negative of given title top margin', () => { vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); - const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(title_attr_spy).toHaveBeenCalledWith('y', -5); + expect(titleAttrSpy).toHaveBeenCalledWith('y', -5); }); it('class is the given css class', () => { vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); - const title_attr_spy = vi.spyOn(fauxTitle, 'attr'); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(title_attr_spy).toHaveBeenCalledWith('class', 'testClass'); + expect(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass'); }); }); From bfe3f309d26f632eee2ed7f2a7543f7258c832d3 Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Thu, 1 Dec 2022 10:09:43 -0800 Subject: [PATCH 26/27] remove typeof --- packages/mermaid/src/accessibility.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts index d70ad00d3..b9e088e0b 100644 --- a/packages/mermaid/src/accessibility.ts +++ b/packages/mermaid/src/accessibility.ts @@ -34,7 +34,7 @@ export function addSVGa11yTitleDescription( a11yDesc: string | null | undefined, baseId: string ) { - if (typeof svg.insert === 'undefined') { + if (svg.insert === undefined) { return; } From 1c9a55936201db54aab405e86603a86840d4b69d Mon Sep 17 00:00:00 2001 From: "Ashley Engelund (weedySeaDragon @ github)" Date: Wed, 7 Dec 2022 10:19:30 -0800 Subject: [PATCH 27/27] common function for a11y; add to renderAsync; + renderAsync spec --- docs/config/setup/modules/mermaidAPI.md | 2 +- packages/mermaid/src/mermaidAPI.spec.ts | 52 +++++++++++++++++++++++++ packages/mermaid/src/mermaidAPI.ts | 23 ++++++++++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 7902c1be9..1b840dcd3 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -90,7 +90,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:968](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L968) ## Functions diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index dd36b1d0c..f9bad66d7 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -734,4 +734,56 @@ describe('mermaidAPI', function () { }); }); }); + + describe('renderAsync', () => { + // Be sure to add async before each test (anonymous) method + + // 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', async () => { + const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo'); + const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription'); + await mermaidAPI.renderAsync(id, diagramText); + expect(a11yDiagramInfo_spy).toHaveBeenCalledWith( + expect.anything(), + expectedDiagramType + ); + expect(a11yTitleDesc_spy).toHaveBeenCalled(); + }); + }); + }); + }); + }); }); diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 1ac03c4f6..a77aed96d 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -519,10 +519,9 @@ const render = function ( // This is the d3 node for the svg element const svgNode = root.select(`${enclosingDivID_selector} svg`); - setA11yDiagramInfo(svgNode, graphType); const a11yTitle = diag.db.getAccTitle?.(); const a11yDescr = diag.db.getAccDescription?.(); - addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id')); + addA11yInfo(graphType, svgNode, a11yTitle, a11yDescr); // ------------------------------------------------------------------------------- // Clean up SVG code @@ -720,6 +719,12 @@ const renderAsync = async function ( throw e; } + // This is the d3 node for the svg element + const svgNode = root.select(`${enclosingDivID_selector} svg`); + const a11yTitle = diag.db.getAccTitle?.(); + const a11yDescr = diag.db.getAccDescription?.(); + addA11yInfo(graphType, svgNode, a11yTitle, a11yDescr); + // ------------------------------------------------------------------------------- // Clean up SVG code root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); @@ -884,6 +889,20 @@ function initialize(options: MermaidConfig = {}) { addDiagrams(); } +/** + * Add accessibility (a11y) information to the diagram. + * + */ +function addA11yInfo( + graphType: string, + svgNode: D3Element, + a11yTitle: string | undefined, + a11yDescr: string | undefined +) { + setA11yDiagramInfo(svgNode, graphType); + addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id')); +} + /** * ## mermaidAPI configuration defaults *