From 85a8accc74eb61af4846748b414aa6efbc66680e Mon Sep 17 00:00:00 2001 From: Donald Pipowitch Date: Sun, 10 Nov 2019 21:31:59 +0100 Subject: [PATCH 1/3] fix 8126: show errors, reset config properly --- addons/a11y/src/components/A11YPanel.tsx | 54 +++++++++++++++++++----- addons/a11y/src/constants.ts | 3 +- addons/a11y/src/index.ts | 9 +++- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/addons/a11y/src/components/A11YPanel.tsx b/addons/a11y/src/components/A11YPanel.tsx index 5f0c1fffb61f..194acca7be6d 100644 --- a/addons/a11y/src/components/A11YPanel.tsx +++ b/addons/a11y/src/components/A11YPanel.tsx @@ -46,26 +46,35 @@ const Incomplete = styled.span<{}>(({ theme }) => ({ color: theme.color.warning, })); +const centeredStyle = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100%', +}; + const Loader = styled(({ className }) => (
Please wait while the accessibility scan is running ...
-))({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: '100%', -}); +))(centeredStyle); Loader.displayName = 'Loader'; -interface A11YPanelState { - status: string; +interface A11YPanelNormalState { + status: 'ready' | 'ran' | 'running'; passes: Result[]; violations: Result[]; incomplete: Result[]; } +interface A11YPanelErrorState { + status: 'error'; + error: unknown; +} + +type A11YPanelState = A11YPanelNormalState | A11YPanelErrorState; + interface A11YPanelProps { active: boolean; api: API; @@ -84,6 +93,7 @@ export class A11YPanel extends Component { api.on(STORY_RENDERED, this.request); api.on(EVENTS.RESULT, this.onUpdate); + api.on(EVENTS.ERROR, this.onError); } componentDidUpdate(prevProps: A11YPanelProps) { @@ -101,6 +111,7 @@ export class A11YPanel extends Component { const { api } = this.props; api.off(STORY_RENDERED, this.request); api.off(EVENTS.RESULT, this.onUpdate); + api.off(EVENTS.ERROR, this.onError); } onUpdate = ({ passes, violations, incomplete }: AxeResults) => { @@ -124,6 +135,13 @@ export class A11YPanel extends Component { ); }; + onError = (error: unknown) => { + this.setState({ + status: 'error', + error, + }); + }; + request = () => { const { api, active } = this.props; @@ -142,8 +160,22 @@ export class A11YPanel extends Component { }; render() { - const { passes, violations, incomplete, status } = this.state; const { active } = this.props; + if (!active) return null; + + // eslint-disable-next-line react/destructuring-assignment + if (this.state.status === 'error') { + const { error } = this.state; + return ( +
+ The accessibility scan encountered an error. +
+ {error} +
+ ); + } + + const { passes, violations, incomplete, status } = this.state; let actionTitle; if (status === 'ready') { @@ -162,7 +194,7 @@ export class A11YPanel extends Component { ); } - return active ? ( + return ( {status === 'running' ? ( @@ -218,6 +250,6 @@ export class A11YPanel extends Component { /> - ) : null; + ); } } diff --git a/addons/a11y/src/constants.ts b/addons/a11y/src/constants.ts index d5b153986461..efabac7a268b 100755 --- a/addons/a11y/src/constants.ts +++ b/addons/a11y/src/constants.ts @@ -6,5 +6,6 @@ export const ADD_ELEMENT = 'ADD_ELEMENT'; export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS'; const RESULT = `${ADDON_ID}/result`; const REQUEST = `${ADDON_ID}/request`; +const ERROR = `${ADDON_ID}/error`; -export const EVENTS = { RESULT, REQUEST }; +export const EVENTS = { RESULT, REQUEST, ERROR }; diff --git a/addons/a11y/src/index.ts b/addons/a11y/src/index.ts index 20c8fc717267..19b7c6a662a2 100644 --- a/addons/a11y/src/index.ts +++ b/addons/a11y/src/index.ts @@ -39,7 +39,8 @@ const run = (element: ElementContext, config: Spec, options: RunOptions) => { restoreScroll: true, } as RunOptions) // cast to RunOptions is necessary because axe types are not up to date ) - .then(report); + .then(report) + .catch(error => addons.getChannel().emit(EVENTS.ERROR, String(error))); }); }; @@ -47,12 +48,18 @@ if (module && module.hot && module.hot.decline) { module.hot.decline(); } +let storedSetup: Setup | null = null; + export const withA11y = makeDecorator({ name: 'withA11Y', parameterName: PARAM_KEY, wrapper: (getStory, context, { parameters }) => { if (parameters) { + storedSetup = setup; setup = parameters as Setup; + } else if (storedSetup) { + setup = storedSetup; + storedSetup = null; } addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options)); From c74b1f6c1ab2881cd344052181e8e24cde049fbe Mon Sep 17 00:00:00 2001 From: Donald Pipowitch Date: Mon, 11 Nov 2019 07:44:52 +0100 Subject: [PATCH 2/3] partially apply configuration --- addons/a11y/README.md | 1 - addons/a11y/src/index.ts | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/a11y/README.md b/addons/a11y/README.md index 584e49f67028..d4d2638a9e6c 100755 --- a/addons/a11y/README.md +++ b/addons/a11y/README.md @@ -56,7 +56,6 @@ import { withA11y } from '@storybook/addon-a11y'; addDecorator(withA11y) addParameters({ a11y: { - // ... axe options element: '#root', // optional selector which element to inspect config: {}, // axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1) options: {} // axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter) diff --git a/addons/a11y/src/index.ts b/addons/a11y/src/index.ts index 19b7c6a662a2..a7f53ec29a73 100644 --- a/addons/a11y/src/index.ts +++ b/addons/a11y/src/index.ts @@ -48,18 +48,20 @@ if (module && module.hot && module.hot.decline) { module.hot.decline(); } -let storedSetup: Setup | null = null; +let storedDefaultSetup: Setup | null = null; export const withA11y = makeDecorator({ name: 'withA11Y', parameterName: PARAM_KEY, wrapper: (getStory, context, { parameters }) => { if (parameters) { - storedSetup = setup; - setup = parameters as Setup; - } else if (storedSetup) { - setup = storedSetup; - storedSetup = null; + if (storedDefaultSetup === null) { + storedDefaultSetup = { ...setup }; + } + Object.assign(setup, parameters as Setup); + } else if (storedDefaultSetup !== null) { + Object.assign(setup, storedDefaultSetup); + storedDefaultSetup = null; } addons.getChannel().on(EVENTS.REQUEST, () => run(setup.element, setup.config, setup.options)); From bacccb9fa70859f70aa5438e860156308ffdc26c Mon Sep 17 00:00:00 2001 From: Donald Pipowitch Date: Mon, 11 Nov 2019 13:01:31 +0100 Subject: [PATCH 3/3] fix tests --- addons/a11y/src/components/A11YPanel.test.js | 10 ++++++---- .../components/__snapshots__/A11YPanel.test.js.snap | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/addons/a11y/src/components/A11YPanel.test.js b/addons/a11y/src/components/A11YPanel.test.js index 5c57664c573c..2f84b31afd39 100644 --- a/addons/a11y/src/components/A11YPanel.test.js +++ b/addons/a11y/src/components/A11YPanel.test.js @@ -63,7 +63,7 @@ function ThemedA11YPanel(props) { } describe('A11YPanel', () => { - it('should register STORY_RENDERED and RESULT updater on mount', () => { + it('should register STORY_RENDERED, RESULT and ERROR updater on mount', () => { // given const api = createApi(); expect(api.on).not.toHaveBeenCalled(); @@ -72,9 +72,10 @@ describe('A11YPanel', () => { mount(); // then - expect(api.on.mock.calls.length).toBe(2); + expect(api.on.mock.calls.length).toBe(3); expect(api.on.mock.calls[0][0]).toBe(STORY_RENDERED); expect(api.on.mock.calls[1][0]).toBe(EVENTS.RESULT); + expect(api.on.mock.calls[2][0]).toBe(EVENTS.ERROR); }); it('should request a run on tab activation', () => { @@ -93,7 +94,7 @@ describe('A11YPanel', () => { expect(wrapper.find(ScrollArea).length).toBe(0); }); - it('should deregister STORY_RENDERED and RESULT updater on unmount', () => { + it('should deregister STORY_RENDERED, RESULT and ERROR updater on unmount', () => { // given const api = createApi(); const wrapper = mount(); @@ -103,9 +104,10 @@ describe('A11YPanel', () => { wrapper.unmount(); // then - expect(api.off.mock.calls.length).toBe(2); + expect(api.off.mock.calls.length).toBe(3); expect(api.off.mock.calls[0][0]).toBe(STORY_RENDERED); expect(api.off.mock.calls[1][0]).toBe(EVENTS.RESULT); + expect(api.off.mock.calls[2][0]).toBe(EVENTS.ERROR); }); it('should update run result', () => { diff --git a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap b/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap index 24e12e704048..2ac873d47536 100644 --- a/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap +++ b/addons/a11y/src/components/__snapshots__/A11YPanel.test.js.snap @@ -177,6 +177,10 @@ exports[`A11YPanel should render report 1`] = ` "storybook/a11y/result", [Function], ], + Array [ + "storybook/a11y/error", + [Function], + ], ], "results": Array [ Object { @@ -187,6 +191,10 @@ exports[`A11YPanel should render report 1`] = ` "type": "return", "value": undefined, }, + Object { + "type": "return", + "value": undefined, + }, ], }, }