From 2ee1b912f58cff4964786ce6586b07390bbed0b3 Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Wed, 14 Apr 2021 08:17:39 -0600 Subject: [PATCH] feat(a11y): allow user to pass custom description for screen readers (#1111) Fixes #1097 --- api/charts.api.md | 4 +- integration/page_objects/common.ts | 10 +++ ...cription-visually-looks-correct-1-snap.png | Bin 0 -> 27601 bytes .../xy_chart/renderer/canvas/xy_chart.tsx | 34 ++++++-- .../xy_chart/state/chart_state.a11y.test.ts | 76 ++++++++++++++++++ .../__snapshots__/chart.test.tsx.snap | 22 ++--- src/specs/constants.ts | 1 + src/specs/settings.tsx | 13 ++- .../test_cases/6_a11y_custom_description.tsx | 42 ++++++++++ stories/test_cases/test_cases.stories.tsx | 1 + 10 files changed, 185 insertions(+), 18 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-test-cases-add-custom-description-visually-looks-correct-1-snap.png create mode 100644 src/chart_types/xy_chart/state/chart_state.a11y.test.ts create mode 100644 stories/test_cases/6_a11y_custom_description.tsx diff --git a/api/charts.api.md b/api/charts.api.md index 82deef6b39..ae3330e87f 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -617,7 +617,7 @@ export const DEFAULT_TOOLTIP_SNAP = true; export const DEFAULT_TOOLTIP_TYPE: "vertical"; // @public (undocumented) -export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth'; +export type DefaultSettingsProps = 'id' | 'chartType' | 'specType' | 'rendering' | 'rotation' | 'resizeDebounce' | 'animateData' | 'debug' | 'tooltip' | 'theme' | 'hideDuplicateAxes' | 'brushAxis' | 'minBrushDelta' | 'externalPointerEvents' | 'showLegend' | 'showLegendExtra' | 'legendPosition' | 'legendMaxDepth' | 'description' | 'useDefaultSummary'; // @public (undocumented) export const DEPTH_KEY = "depth"; @@ -1739,6 +1739,7 @@ export interface SettingsSpec extends Spec, LegendSpec { debug: boolean; // @alpha debugState?: boolean; + description?: string; // @alpha externalPointerEvents: ExternalPointerEventsSettings; hideDuplicateAxes: boolean; @@ -1769,6 +1770,7 @@ export interface SettingsSpec extends Spec, LegendSpec { roundHistogramBrushValues?: boolean; theme?: PartialTheme | PartialTheme[]; tooltip: TooltipSettings; + useDefaultSummary: boolean; // (undocumented) xDomain?: CustomXDomain; } diff --git a/integration/page_objects/common.ts b/integration/page_objects/common.ts index 71983806e1..8db68687b7 100644 --- a/integration/page_objects/common.ts +++ b/integration/page_objects/common.ts @@ -474,6 +474,16 @@ class CommonPage { }); return accessibilitySnapshot; } + + /** + * Get HTML for element to test aria labels etc + */ + // eslint-disable-next-line class-methods-use-this + async getElementHTML(url: string) { + await this.loadElementFromURL(url); + // https://github.com/puppeteer/puppeteer/issues/406#issuecomment-323555639 + return await page.evaluate(() => new XMLSerializer().serializeToString(document)); + } } export const common = new CommonPage(); diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-test-cases-add-custom-description-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-test-cases-add-custom-description-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6061f7504ff7a9e7d9b1b9f3bc4c9b0b6529f7 GIT binary patch literal 27601 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 literal 0 HcmV?d00001 diff --git a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx index 40a174a841..e9b35ea5db 100644 --- a/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx +++ b/src/chart_types/xy_chart/renderer/canvas/xy_chart.tsx @@ -25,6 +25,7 @@ import { LegendItem } from '../../../../common/legend'; import { onChartRendered } from '../../../../state/actions/chart'; import { GlobalChartState } from '../../../../state/chart_state'; import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions'; +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; import { getChartRotationSelector } from '../../../../state/selectors/get_chart_rotation'; import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; @@ -78,6 +79,9 @@ export interface ReactiveChartStateProps { annotationSpecs: AnnotationSpec[]; panelGeoms: PanelGeoms; seriesTypes: Set; + description?: string; + useDefaultSummary: boolean; + chartId: string; } interface ReactiveChartDispatchProps { @@ -155,6 +159,9 @@ class XYChartComponent extends React.Component { isChartEmpty, chartContainerDimensions: { width, height }, seriesTypes, + description, + useDefaultSummary, + chartId, } = this.props; if (!initialized || isChartEmpty) { @@ -164,7 +171,7 @@ class XYChartComponent extends React.Component { const chartSeriesTypes = seriesTypes.size > 1 ? `Mixed chart: ${[...seriesTypes].join(' and ')} chart` : `${[...seriesTypes]} chart`; - + const chartIdDescription = `${chartId}--description`; return (
{ }} // eslint-disable-next-line jsx-a11y/no-interactive-element-to-noninteractive-role role="presentation" + {...(description ? { 'aria-describedby': chartIdDescription } : {})} > -
-
Chart type
-
{chartSeriesTypes}
-
+ {(description || useDefaultSummary) && ( +
+ {description &&

{description}

} + {useDefaultSummary && ( +
+
Chart type
+
{chartSeriesTypes}
+
+ )} +
+ )}
); @@ -237,6 +252,9 @@ const DEFAULT_PROPS: ReactiveChartStateProps = { annotationSpecs: [], panelGeoms: [], seriesTypes: new Set(), + description: undefined, + useDefaultSummary: true, + chartId: '', }; const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { @@ -245,11 +263,12 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { } const { geometries, geometriesIndex } = computeSeriesGeometriesSelector(state); + const { debug, description, useDefaultSummary } = getSettingsSpecSelector(state); return { initialized: true, isChartEmpty: isChartEmptySelector(state), - debug: getSettingsSpecSelector(state).debug, + debug, geometries, geometriesIndex, theme: getChartThemeSelector(state), @@ -266,6 +285,9 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { annotationSpecs: getAnnotationSpecsSelector(state), panelGeoms: computePanelsSelectors(state), seriesTypes: getSeriesTypes(state), + description, + useDefaultSummary, + chartId: getChartIdSelector(state), }; }; diff --git a/src/chart_types/xy_chart/state/chart_state.a11y.test.ts b/src/chart_types/xy_chart/state/chart_state.a11y.test.ts new file mode 100644 index 0000000000..cc296f7c06 --- /dev/null +++ b/src/chart_types/xy_chart/state/chart_state.a11y.test.ts @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Store } from 'redux'; + +import { MockGlobalSpec, MockSeriesSpec } from '../../../mocks/specs'; +import { MockStore } from '../../../mocks/store/store'; +import { GlobalChartState } from '../../../state/chart_state'; +import { getSettingsSpecSelector } from '../../../state/selectors/get_settings_specs'; + +describe('custom description for screen readers', () => { + let store: Store; + beforeEach(() => { + store = MockStore.default(); + MockStore.addSpecs( + [ + MockSeriesSpec.bar({ + data: [ + { x: 1, y: 10 }, + { x: 2, y: 5 }, + ], + }), + MockGlobalSpec.settings(), + ], + store, + ); + }); + it('should test defaults', () => { + const state = store.getState(); + const { description, useDefaultSummary } = getSettingsSpecSelector(state); + expect(description).toBeUndefined(); + expect(useDefaultSummary).toBeTrue(); + }); + it('should allow user to set a custom description for chart', () => { + MockStore.addSpecs( + [ + MockGlobalSpec.settings({ + description: 'This is sample Kibana data', + }), + ], + store, + ); + const state = store.getState(); + const { description } = getSettingsSpecSelector(state); + expect(description).toBe('This is sample Kibana data'); + }); + it('should be able to disable generated descriptions', () => { + MockStore.addSpecs( + [ + MockGlobalSpec.settings({ + useDefaultSummary: false, + }), + ], + store, + ); + const state = store.getState(); + const { useDefaultSummary } = getSettingsSpecSelector(state); + expect(useDefaultSummary).toBe(false); + }); +}); diff --git a/src/components/__snapshots__/chart.test.tsx.snap b/src/components/__snapshots__/chart.test.tsx.snap index 6c1e2f571a..995341dbc1 100644 --- a/src/components/__snapshots__/chart.test.tsx.snap +++ b/src/components/__snapshots__/chart.test.tsx.snap @@ -54,7 +54,7 @@ exports[`Chart should render the legend name test 1`] = ` - + @@ -72,17 +72,19 @@ exports[`Chart should render the legend name test 1`] = ` - +
-
-
- Chart type -
-
- bar chart -
-
+
+
+
+ Chart type +
+
+ bar chart +
+
+
diff --git a/src/specs/constants.ts b/src/specs/constants.ts index ed3f70674f..f6c7c98b19 100644 --- a/src/specs/constants.ts +++ b/src/specs/constants.ts @@ -155,6 +155,7 @@ export const DEFAULT_SETTINGS_SPEC: SettingsSpec = { baseTheme: LIGHT_THEME, brushAxis: BrushAxis.X, minBrushDelta: 2, + useDefaultSummary: true, ...DEFAULT_LEGEND_CONFIG, }; diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 5eda362448..44ac0599e7 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -548,6 +548,15 @@ export interface SettingsSpec extends Spec, LegendSpec { * Render component for no results UI */ noResults?: ComponentType | ReactChild; + /** + * User can provide a custom description to be read by a screen reader about their chart + */ + description?: string; + /** + * Disable the automated charts series types from being provided for screen readers + * @defaultValue true + */ + useDefaultSummary: boolean; } /** @@ -608,7 +617,9 @@ export type DefaultSettingsProps = | 'showLegend' | 'showLegendExtra' | 'legendPosition' - | 'legendMaxDepth'; + | 'legendMaxDepth' + | 'description' + | 'useDefaultSummary'; /** @public */ export type SettingsSpecProps = Partial>; diff --git a/stories/test_cases/6_a11y_custom_description.tsx b/stories/test_cases/6_a11y_custom_description.tsx new file mode 100644 index 0000000000..118a27b57f --- /dev/null +++ b/stories/test_cases/6_a11y_custom_description.tsx @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { boolean, text } from '@storybook/addon-knobs'; +import React from 'react'; + +import { AreaSeries, Chart, ScaleType, Settings } from '../../src'; +import { KIBANA_METRICS } from '../../src/utils/data_samples/test_dataset_kibana'; + +export const Example = () => { + const automatedSeries = boolean('Use the default generated series types of charts for screen readers', true); + const customDescriptionForScreenReaders = text('custom description for screen readers', ''); + return ( + + + + + ); +}; diff --git a/stories/test_cases/test_cases.stories.tsx b/stories/test_cases/test_cases.stories.tsx index 3f75475915..b9c2885a71 100644 --- a/stories/test_cases/test_cases.stories.tsx +++ b/stories/test_cases/test_cases.stories.tsx @@ -31,3 +31,4 @@ export { Example as chromePathBugFix } from './2_chrome_path_bug_fix'; export { Example as noAxesAnnotationBugFix } from './3_no_axes_annotation'; export { Example as filterZerosInLogFitDomain } from './4_filter_zero_values_log'; export { Example as legendScrollBarSizing } from './5_legend_scroll_bar_sizing'; +export { Example as addCustomDescription } from './6_a11y_custom_description';