set describeby to accessible description element id

This commit is contained in:
Ashley Engelund (weedySeaDragon @ github) 2022-11-17 15:50:52 -08:00
parent 68b1805c40
commit b51759d36e
2 changed files with 112 additions and 65 deletions

View File

@ -33,46 +33,95 @@ describe('accessibility', () => {
expect(noInsert_attr_spy).not.toHaveBeenCalled();
});
// ----------------
// Convenience functions to DRY up the spec
function expectAriaLabelledByIsTitleId(
svgD3Node: any,
title: string | null | undefined,
desc: string | null | undefined,
givenId: string
) {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
}
function expectAriaDescribedByIsDescId(
svgD3Node: any,
title: string | null | undefined,
desc: string | null | undefined,
givenId: string
) {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node);
addSVGa11yTitleDescription(svgD3Node, title, desc, givenId);
expect(svg_attr_spy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`);
}
function a11yTitleTagInserted(
svgD3Node: any,
title: string | null | undefined,
desc: string | null | undefined,
givenId: string,
callNumber: number
) {
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title);
}
function a11yDescTagInserted(
svgD3Node: any,
title: string | null | undefined,
desc: string | null | undefined,
givenId: string,
callNumber: number
) {
a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc);
}
function a11yTagInserted(
svgD3Node: any,
title: string | null | undefined,
desc: string | null | undefined,
givenId: string,
callNumber: number,
expectedPrefix: string,
expectedText: string | null | undefined
) {
const faux_insertedD3 = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_insertedD3);
// @ts-ignore Required to easily handle the d3 select types
const title_attr_spy = vi.spyOn(faux_insertedD3, 'attr').mockReturnValue(faux_insertedD3);
const title_text_spy = vi.spyOn(faux_insertedD3, 'text');
addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId);
expect(svg_insert_spy).toHaveBeenCalledWith(expectedPrefix, ':first-child');
expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`);
expect(title_text_spy).toHaveBeenNthCalledWith(callNumber, expectedText);
}
// ----------------
describe('given an a11y title', () => {
const a11yTitle = 'a11y title';
describe('given an a11y description', () => {
const a11yDesc = 'a11y description';
it('sets aria-labelledby to the title id and the description id inserted as children', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).toHaveBeenCalledWith(
'aria-labelledby',
`chart-title-${givenId} chart-desc-${givenId}`
);
it('sets aria-labelledby to the title id inserted as a child', () => {
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
it('sets aria-describedby to the description id inserted as a child', () => {
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
const faux_title = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title);
// @ts-ignore Required to easily handle the d3 select types
const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title);
const title_text_spy = vi.spyOn(faux_title, 'text');
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child');
expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId);
expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y description');
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2);
});
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
const faux_desc = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc);
// @ts-ignore Required to easily handle the d3 select types
const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc);
const desc_text_spy = vi.spyOn(faux_desc, 'text');
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child');
expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId);
expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description');
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
});
@ -80,23 +129,18 @@ describe('accessibility', () => {
const a11yDesc = undefined;
it('sets aria-labelledby to the title id inserted as a child', () => {
expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
it('no aria-describedby is set', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`);
expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
});
it('inserts a title tag as the first child with the text set to the accTitle given', () => {
const faux_title = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title);
// @ts-ignore Required to easily handle the d3 select types
const title_attr_spy = vi.spyOn(faux_title, 'attr').mockReturnValue(faux_title);
const title_text_spy = vi.spyOn(faux_title, 'text');
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_insert_spy).toHaveBeenCalledWith('title', ':first-child');
expect(title_attr_spy).toHaveBeenCalledWith('id', `chart-title-` + givenId);
expect(title_text_spy).toHaveBeenNthCalledWith(1, 'a11y title');
a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
it('no description tag is inserted', () => {
@ -114,6 +158,13 @@ describe('accessibility', () => {
describe('given an a11y description', () => {
const a11yDesc = 'a11y description';
it('no aria-labelledby is set', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
});
it('no title tag inserted', () => {
const faux_title = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title);
@ -121,30 +172,32 @@ describe('accessibility', () => {
expect(svg_insert_spy).not.toHaveBeenCalledWith('title', ':first-child');
});
it('sets aria-labelledby to the description id inserted as a child', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).toHaveBeenCalledWith('aria-labelledby', `chart-desc-${givenId}`);
it('sets aria-describedby to the description id inserted as a child', () => {
expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId);
});
it('inserts a desc tag as a child with the text set to accDescription given', () => {
const faux_desc = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_desc);
// @ts-ignore Required to easily handle the d3 select types
const desc_attr_spy = vi.spyOn(faux_desc, 'attr').mockReturnValue(faux_desc);
const desc_text_spy = vi.spyOn(faux_desc, 'text');
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_insert_spy).toHaveBeenCalledWith('desc', ':first-child');
expect(desc_attr_spy).toHaveBeenCalledWith('id', `chart-desc-` + givenId);
expect(desc_text_spy).toHaveBeenNthCalledWith(1, 'a11y description');
it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => {
a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1);
});
});
describe('no a11y description', () => {
const a11yDesc = undefined;
it('no aria-labelledby is set', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything());
});
it('no aria-describedby is set', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).not.toHaveBeenCalledWith('aria-describedby', expect.anything());
});
it('no title tag inserted', () => {
const faux_title = new MockedD3();
const svg_insert_spy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(faux_title);
@ -158,13 +211,6 @@ describe('accessibility', () => {
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_insert_spy).not.toHaveBeenCalledWith('desc', ':first-child');
});
it('no aria-labelledby is set', () => {
// @ts-ignore Required to easily handle the d3 select types
const svg_attr_spy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode);
addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId);
expect(svg_attr_spy).not.toHaveBeenCalled();
});
});
});
});

View File

@ -40,14 +40,15 @@ export function addSVGa11yTitleDescription(
return;
}
const titleId = a11yTitle ? 'chart-title-' + baseId : null;
const descId = a11yDesc ? 'chart-desc-' + baseId : null;
if (a11yTitle || a11yDesc) {
svg.attr('aria-labelledby', compact([titleId, descId]).join(' '));
if (a11yDesc) {
const descId = 'chart-desc-' + baseId;
svg.attr('aria-describedby', descId);
svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc);
}
if (a11yTitle) {
const titleId = 'chart-title-' + baseId;
svg.attr('aria-labelledby', titleId);
svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle);
}
} else {