Merge pull request #3808 from weedySeaDragon/feat/3626-aria-descBy-roledescription-mocks
Feat: Add aria-describedby, aria-roledescription
This commit is contained in:
commit
90d9724d1a
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Mocked class diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
|
@ -1,79 +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;
|
||||
},
|
||||
node() {
|
||||
return {
|
||||
getBBox() {
|
||||
return {
|
||||
x: 5,
|
||||
y: 10,
|
||||
height: 15,
|
||||
width: 20,
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Mocked git (graph) diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Mocked pie (picChart) diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Mocked requirement diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -70,6 +70,7 @@
|
|||
"rect",
|
||||
"rects",
|
||||
"redmine",
|
||||
"roledescription",
|
||||
"sandboxed",
|
||||
"setupgraphviewbox",
|
||||
"shiki",
|
||||
|
|
|
@ -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 = [...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(
|
||||
`
|
||||
|
|
|
@ -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 = [...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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,18 +225,7 @@ 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.
|
||||
|
||||
**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
|
||||
|
||||
|
|
|
@ -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
|
||||
<svg
|
||||
aria-roledescription="stateDiagram"
|
||||
class="statediagram"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid-1668720491568"
|
||||
></svg>
|
||||
```
|
||||
|
||||
**When these two options are defined, they will add a corresponding `<title>` 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</title>
|
||||
<desc id="chart-desc-mermaid-1668725057758">This is an accessible description</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
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]
|
||||
|
||||
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]
|
||||
|
||||
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
|
||||
<svg
|
||||
aria-labelledby="chart-title-mermaid_382ee221"
|
||||
aria-describedby="chart-desc-mermaid_382ee221"
|
||||
aria-roledescription="flowchart-v2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid_382ee221"
|
||||
>
|
||||
<title id="chart-title-mermaid_382ee221">Big decisions</title>
|
||||
<desc id="chart-desc-mermaid_382ee221">Bob's Burgers process for making big decisions</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
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[Hard] -->|Text| B(Round)
|
||||
B --> C{Decision}
|
||||
C -->|One| D[Result 1]
|
||||
|
||||
A[Identify Big Descision] --> B{Make Big Decision}
|
||||
B --> D[Be done]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
accTitle: Big decisions
|
||||
|
||||
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[Hard] -->|Text| B(Round)
|
||||
B --> C{Decision}
|
||||
C -->|One| D[Result 1]
|
||||
|
||||
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
|
||||
<svg
|
||||
aria-labelledby="chart-title-mermaid_382ee221"
|
||||
aria-describedby="chart-desc-mermaid_382ee221"
|
||||
aria-roledescription="flowchart-v2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid_382ee221"
|
||||
>
|
||||
<title id="chart-title-mermaid_382ee221">Big decisions</title>
|
||||
<desc id="chart-desc-mermaid_382ee221">
|
||||
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.
|
||||
</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
```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
|
||||
|
||||
```
|
||||
|
|
|
@ -12,6 +12,16 @@
|
|||
|
||||
Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
|
||||
|
||||
## Type Aliases
|
||||
|
||||
### D3Element
|
||||
|
||||
Ƭ **D3Element**: `any`
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L72)
|
||||
|
||||
## Variables
|
||||
|
||||
### mermaidAPI
|
||||
|
@ -80,7 +90,7 @@ mermaid.initialize(config);
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:939](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L939)
|
||||
[mermaidAPI.ts:968](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L968)
|
||||
|
||||
## Functions
|
||||
|
||||
|
@ -111,7 +121,7 @@ Return the last node appended
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:284](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L284)
|
||||
[mermaidAPI.ts:285](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L285)
|
||||
|
||||
---
|
||||
|
||||
|
@ -137,7 +147,7 @@ the cleaned up svgCode
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:235](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L235)
|
||||
[mermaidAPI.ts:236](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L236)
|
||||
|
||||
---
|
||||
|
||||
|
@ -163,7 +173,7 @@ the string with all the user styles
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L164)
|
||||
[mermaidAPI.ts:165](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L165)
|
||||
|
||||
---
|
||||
|
||||
|
@ -186,7 +196,7 @@ the string with all the user styles
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:212](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L212)
|
||||
[mermaidAPI.ts:213](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L213)
|
||||
|
||||
---
|
||||
|
||||
|
@ -213,7 +223,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L148)
|
||||
[mermaidAPI.ts:149](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L149)
|
||||
|
||||
---
|
||||
|
||||
|
@ -233,7 +243,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:129](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L129)
|
||||
|
||||
---
|
||||
|
||||
|
@ -253,7 +263,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:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L100)
|
||||
|
||||
---
|
||||
|
||||
|
@ -279,7 +289,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
|
||||
[mermaidAPI.ts:264](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L264)
|
||||
|
||||
---
|
||||
|
||||
|
@ -305,4 +315,4 @@ Remove any existing elements from the given document
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:335](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L335)
|
||||
[mermaidAPI.ts:336](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L336)
|
||||
|
|
|
@ -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 (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);
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
import { MockedD3 } from './tests/MockedD3';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility';
|
||||
import { D3Element } from './mermaidAPI';
|
||||
|
||||
describe('accessibility', () => {
|
||||
const fauxSvgNode = new MockedD3();
|
||||
|
||||
describe('setA11yDiagramInfo', () => {
|
||||
it('sets the aria-roledescription to the diagram type', () => {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
setA11yDiagramInfo(fauxSvgNode, '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 svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
setA11yDiagramInfo(fauxSvgNode, '');
|
||||
expect(svgAttrSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('addSVGa11yTitleDescription', () => {
|
||||
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 noInsertAttrSpy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg);
|
||||
addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId);
|
||||
expect(noInsertAttrSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// ----------------
|
||||
// Convenience functions to DRY up the spec
|
||||
|
||||
function expectAriaLabelledByIsTitleId(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
givenId: string
|
||||
) {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
|
||||
}
|
||||
|
||||
function expectAriaDescribedByIsDescId(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
givenId: string
|
||||
) {
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
|
||||
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
|
||||
expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
|
||||
}
|
||||
|
||||
function a11yTitleTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
givenId: string,
|
||||
callNumber: number
|
||||
) {
|
||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
|
||||
}
|
||||
|
||||
function a11yDescTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
givenId: string,
|
||||
callNumber: number
|
||||
) {
|
||||
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
|
||||
}
|
||||
|
||||
function a11yTagInserted(
|
||||
svgD3Node: D3Element,
|
||||
title: string | null | undefined,
|
||||
desc: string | null | undefined,
|
||||
givenId: string,
|
||||
callNumber: number,
|
||||
expectedPrefix: string,
|
||||
expectedText: string | null | undefined
|
||||
) {
|
||||
const fauxInsertedD3 = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3);
|
||||
// @ts-ignore Required to easily handle the d3 select types
|
||||
const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3);
|
||||
const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text');
|
||||
|
||||
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
|
||||
expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
|
||||
expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
|
||||
expect(titleTextSpy).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 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', () => {
|
||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
|
||||
});
|
||||
|
||||
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('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 svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
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', () => {
|
||||
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
|
||||
});
|
||||
|
||||
it('no description tag is inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no a11y title', () => {
|
||||
const a11yTitle = undefined;
|
||||
|
||||
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 svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
|
||||
});
|
||||
|
||||
it('no title tag inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
});
|
||||
|
||||
it('sets aria-describedby to the description id inserted as a child', () => {
|
||||
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
});
|
||||
|
||||
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 svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
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 svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
|
||||
});
|
||||
|
||||
it('no title tag inserted', () => {
|
||||
const fauxTitle = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child');
|
||||
});
|
||||
|
||||
it('no description tag inserted', () => {
|
||||
const fauxDesc = new MockedD3();
|
||||
const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc);
|
||||
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
|
||||
expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Accessibility (a11y) functions, types, helpers
|
||||
*
|
||||
*/
|
||||
import { D3Element } from './mermaidAPI';
|
||||
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
|
||||
/**
|
||||
* 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: D3Element, diagramType: string | null | undefined) {
|
||||
if (!isEmpty(diagramType)) {
|
||||
svg.attr('aria-roledescription', diagramType);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add an accessible title and/or description element to a chart.
|
||||
* The title is usually not displayed and the description is never displayed.
|
||||
*
|
||||
* The following charts display their title as a visual and accessibility element: gantt
|
||||
*
|
||||
* @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(
|
||||
svg: D3Element,
|
||||
a11yTitle: string | null | undefined,
|
||||
a11yDesc: string | null | undefined,
|
||||
baseId: string
|
||||
) {
|
||||
if (svg.insert === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (a11yTitle || a11yDesc) {
|
||||
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 {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ export interface InjectUtils {
|
|||
export interface DiagramDb {
|
||||
clear?: () => void;
|
||||
setDiagramTitle?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
getAccDescription?: () => string;
|
||||
}
|
||||
|
||||
export interface DiagramDefinition {
|
||||
|
|
|
@ -8,7 +8,6 @@ import * as configApi from '../../config';
|
|||
import assignWithDepth from '../../assignWithDepth';
|
||||
import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
let globalBoundaryMaxX = 0,
|
||||
globalBoundaryMaxY = 0;
|
||||
|
@ -675,7 +674,6 @@ export const draw = function (_text, id, _version, diagObj) {
|
|||
(height + extraVertForTitle)
|
||||
);
|
||||
|
||||
addSVGAccessibilityFields(parser.yy, diagram, id);
|
||||
log.debug(`models:`, box);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import { curveLinear } from 'd3';
|
|||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox';
|
||||
import common from '../common/common';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
const sanitizeText = (txt) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
|
@ -451,7 +450,6 @@ export const draw = function (text, id, _version, diagObj) {
|
|||
}
|
||||
}
|
||||
|
||||
addSVGAccessibilityFields(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) {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { log } from '../../logger';
|
|||
import svgDraw from './svgDraw';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import { getConfig } from '../../config';
|
||||
import addSVGAccessibilityFields 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);
|
||||
addSVGAccessibilityFields(diagObj.db, diagram, id);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -6,7 +6,6 @@ import { log } from '../../logger';
|
|||
import utils from '../../utils';
|
||||
import erMarkers from './erMarkers';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
import { parseGenericTypes } from '../common/common';
|
||||
import { v4 as uuid4 } from 'uuid';
|
||||
|
||||
|
@ -642,8 +641,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}`);
|
||||
|
||||
addSVGAccessibilityFields(diagObj.db, svg, id);
|
||||
}; // draw
|
||||
|
||||
/**
|
||||
|
|
|
@ -323,10 +323,11 @@ 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}"`;
|
||||
|
||||
it('should allow for a accessibility title and description (accDescr)', function () {
|
||||
erDiagram.parser.parse(
|
||||
`erDiagram
|
||||
accTitle: graph title
|
||||
|
@ -337,20 +338,19 @@ 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}"`;
|
||||
|
||||
it('parses a multi line description (accDescr)', function () {
|
||||
erDiagram.parser.parse(
|
||||
`erDiagram
|
||||
accTitle: graph title
|
||||
accDescr {
|
||||
this graph is about stuff
|
||||
accDescr { this graph is
|
||||
about
|
||||
stuff
|
||||
}\n
|
||||
${line1}`
|
||||
);
|
||||
expect(erDb.getAccTitle()).toBe('graph title');
|
||||
expect(erDb.getAccDescription()).toBe('this graph is about stuff');
|
||||
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 () {
|
||||
|
|
|
@ -11,7 +11,6 @@ import { log } from '../../logger';
|
|||
import common, { evaluate } from '../common/common';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
const conf = {};
|
||||
export const setConf = function (cnf) {
|
||||
|
@ -431,9 +430,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
|
||||
addSVGAccessibilityFields(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);
|
||||
|
|
|
@ -9,7 +9,6 @@ import common, { evaluate } from '../common/common';
|
|||
import { interpolateToCurve, getStylesFromArray } from '../../utils';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox';
|
||||
import flowChartShapes from './flowChartShapes';
|
||||
import addSVGAccessibilityFields 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
|
||||
addSVGAccessibilityFields(diagObj.db, svg, id);
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
const element = root.select('#' + id + ' g');
|
||||
render(element, g);
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
import common from '../common/common';
|
||||
import { getConfig } from '../../config';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields 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');
|
||||
|
||||
addSVGAccessibilityFields(diagObj.db, svg, id);
|
||||
|
||||
/**
|
||||
* @param tasks
|
||||
* @param pageWidth
|
||||
|
|
|
@ -2,7 +2,6 @@ import { select } from 'd3';
|
|||
import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
|
||||
import { log } from '../../logger';
|
||||
import utils from '../../utils';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
let allCommitsDict = {};
|
||||
|
||||
|
@ -506,9 +505,6 @@ 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);
|
||||
|
||||
drawCommits(diagram, allCommitsDict, false);
|
||||
if (gitGraphConfig.showBranches) {
|
||||
drawBranches(diagram, branches);
|
||||
|
|
|
@ -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 addSVGAccessibilityFields 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);
|
||||
|
||||
addSVGAccessibilityFields(diagObj.db, diagram, id);
|
||||
// Set viewBox
|
||||
elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import { configureSvgSize } from '../../setupGraphViewbox';
|
|||
import common from '../common/common';
|
||||
import markers from './requirementMarkers';
|
||||
import { getConfig } from '../../config';
|
||||
import addSVGAccessibilityFields 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
|
||||
addSVGAccessibilityFields(diagObj.db, svg, id);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,7 +9,6 @@ import * as configApi from '../../config';
|
|||
import assignWithDepth from '../../assignWithDepth';
|
||||
import utils from '../../utils';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
import Diagram from '../../Diagram';
|
||||
|
||||
let conf = {};
|
||||
|
@ -904,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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { log } from '../../logger';
|
|||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import common from '../common/common';
|
||||
import utils from '../../utils';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
import {
|
||||
DEFAULT_DIAGRAM_DIRECTION,
|
||||
DEFAULT_NESTED_DOC_DIR,
|
||||
|
@ -470,7 +470,6 @@ export const draw = function (text, id, _version, diag) {
|
|||
label.insertBefore(rect, label.firstChild);
|
||||
// }
|
||||
}
|
||||
addSVGAccessibilityFields(diag.db, svg, id);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -6,7 +6,6 @@ import common from '../common/common';
|
|||
import { drawState, addTitleAndBox, drawEdge } from './shapes';
|
||||
import { getConfig } from '../../config';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields 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
|
||||
);
|
||||
addSVGAccessibilityFields(diagObj.db, diagram, id);
|
||||
};
|
||||
const getLabelWidth = (text) => {
|
||||
return text ? text.length * conf.fontSizeFactor : 1;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { select } from 'd3';
|
|||
import svgDraw from './svgDraw';
|
||||
import { getConfig } from '../../config';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox';
|
||||
import addSVGAccessibilityFields from '../../accessibility';
|
||||
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
@ -121,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 = {
|
||||
|
|
|
@ -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:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
|
@ -213,18 +222,7 @@ import {
|
|||
} from '../../commonDb';
|
||||
```
|
||||
|
||||
For rendering the accessibility tags you have again an existing function you can use.
|
||||
|
||||
**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
|
||||
|
||||
|
|
|
@ -4,83 +4,151 @@
|
|||
|
||||
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:
|
||||
|
||||
- `accTitle: "Your Accessibility Title"` or
|
||||
- `accDescr: "Your Accessibility Description"`
|
||||
|
||||
**When these two options are defined, they will add a corresponding `<title>` and `<desc>` tag in the SVG.**
|
||||
|
||||
Let us take a look at the following example with a flowchart diagram:
|
||||
|
||||
```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]
|
||||
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
|
||||
<svg
|
||||
aria-roledescription="stateDiagram"
|
||||
class="statediagram"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid-1668720491568"
|
||||
></svg>
|
||||
```
|
||||
|
||||
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:
|
||||
### Accessible Title and Description
|
||||
|
||||
![Accessibility options rendered inside SVG](img/accessibility-div-example.png)
|
||||
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.
|
||||
|
||||
### Multi-line Accessibility title/description
|
||||
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.
|
||||
|
||||
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 `}`.
|
||||
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").
|
||||
|
||||
`accTitle: My single line title value` (**_single line format_**)
|
||||
_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_
|
||||
|
||||
vs
|
||||
```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</title>
|
||||
<desc id="chart-desc-mermaid-1668725057758">This is an accessible description</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**)
|
||||
Details for the syntax follow.
|
||||
|
||||
Let us look at it in the following example, with same flowchart:
|
||||
#### 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
|
||||
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
|
||||
<svg
|
||||
aria-labelledby="chart-title-mermaid_382ee221"
|
||||
aria-describedby="chart-desc-mermaid_382ee221"
|
||||
aria-roledescription="flowchart-v2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid_382ee221"
|
||||
>
|
||||
<title id="chart-title-mermaid_382ee221">Big decisions</title>
|
||||
<desc id="chart-desc-mermaid_382ee221">Bob's Burgers process for making big decisions</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
- 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: 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[Hard] -->|Text| B(Round)
|
||||
B --> C{Decision}
|
||||
C -->|One| D[Result 1]
|
||||
|
||||
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
|
||||
<svg
|
||||
aria-labelledby="chart-title-mermaid_382ee221"
|
||||
aria-describedby="chart-desc-mermaid_382ee221"
|
||||
aria-roledescription="flowchart-v2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
id="mermaid_382ee221"
|
||||
>
|
||||
<title id="chart-title-mermaid_382ee221">Big decisions</title>
|
||||
<desc id="chart-desc-mermaid_382ee221">
|
||||
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.
|
||||
</desc>
|
||||
</svg>
|
||||
```
|
||||
|
||||
#### Class Diagram
|
||||
#### Sample Code Snippets for other diagram types
|
||||
|
||||
##### Class Diagram
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
|
@ -90,18 +158,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 +171,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 +189,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 +224,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the
|
|||
|
||||
```
|
||||
|
||||
#### Requirement Diagram
|
||||
##### Requirement Diagram
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
@ -187,22 +246,43 @@ 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
|
||||
|
||||
```
|
||||
|
|
|
@ -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,106 @@ 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import DOMPurify from 'dompurify';
|
|||
import { MermaidConfig } from './config.type';
|
||||
import { evaluate } from './diagrams/common/common';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility';
|
||||
|
||||
// diagram names that support classDef statements
|
||||
const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2'];
|
||||
|
@ -68,7 +69,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;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -371,7 +372,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
|
||||
|
@ -479,12 +480,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;
|
||||
|
@ -501,6 +503,7 @@ const render = function (
|
|||
idSelector
|
||||
);
|
||||
|
||||
// svg is a HTML element (not a d3 node)
|
||||
const style1 = document.createElement('style');
|
||||
style1.innerHTML = rules;
|
||||
svg.insertBefore(style1, firstChild);
|
||||
|
@ -514,6 +517,12 @@ const render = 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);
|
||||
|
@ -710,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);
|
||||
|
@ -755,7 +770,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) {
|
||||
|
@ -874,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
|
||||
*
|
||||
|
|
|
@ -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<string, string | null>();
|
||||
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;
|
||||
});
|
||||
|
|
|
@ -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 svgAppendSpy = vi.spyOn(svg, 'append');
|
||||
utils.insertTitle(svg, 'testClass', 0, '');
|
||||
expect(svgAppendSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('appends the title as a text item with the given title text', function () {
|
||||
const svgAppendSpy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle);
|
||||
const titleTextSpy = 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(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 titleAttrSpy = vi.spyOn(fauxTitle, 'attr');
|
||||
|
||||
utils.insertTitle(svg, 'testClass', 5, 'test title');
|
||||
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 titleAttrSpy = vi.spyOn(fauxTitle, 'attr');
|
||||
|
||||
utils.insertTitle(svg, 'testClass', 5, 'test title');
|
||||
expect(titleAttrSpy).toHaveBeenCalledWith('y', -5);
|
||||
});
|
||||
|
||||
it('class is the given css class', () => {
|
||||
vi.spyOn(svg, 'append').mockReturnValue(fauxTitle);
|
||||
const titleAttrSpy = vi.spyOn(fauxTitle, 'attr');
|
||||
|
||||
utils.insertTitle(svg, 'testClass', 5, 'test title');
|
||||
expect(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -913,7 +913,7 @@ export function getErrorMessage(error: unknown): string {
|
|||
}
|
||||
|
||||
/**
|
||||
* Appends <text> element with the given title, centered.
|
||||
* Appends <text> element with the given title and css class.
|
||||
*
|
||||
* @param parent - d3 svg object to append title to
|
||||
* @param cssClass - CSS class for the <text> element containing the title
|
||||
|
|
Loading…
Reference in New Issue