Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-A11y: Show errors, reset config properly #8779

Merged
merged 3 commits into from Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion addons/a11y/README.md
Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions addons/a11y/src/components/A11YPanel.test.js
Expand Up @@ -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();
Expand All @@ -72,9 +72,10 @@ describe('A11YPanel', () => {
mount(<ThemedA11YPanel api={api} />);

// 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', () => {
Expand All @@ -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(<ThemedA11YPanel api={api} />);
Expand All @@ -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', () => {
Expand Down
54 changes: 43 additions & 11 deletions addons/a11y/src/components/A11YPanel.tsx
Expand Up @@ -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 }) => (
<div className={className}>
<Icon inline icon="sync" status="running" /> Please wait while the accessibility scan is running
...
</div>
))({
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;
Expand All @@ -84,6 +93,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {

api.on(STORY_RENDERED, this.request);
api.on(EVENTS.RESULT, this.onUpdate);
api.on(EVENTS.ERROR, this.onError);
}

componentDidUpdate(prevProps: A11YPanelProps) {
Expand All @@ -101,6 +111,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
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) => {
Expand All @@ -124,6 +135,13 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
);
};

onError = (error: unknown) => {
this.setState({
status: 'error',
error,
});
};

request = () => {
const { api, active } = this.props;

Expand All @@ -142,8 +160,22 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
};

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 (
<div style={centeredStyle}>
The accessibility scan encountered an error.
<br />
{error}
</div>
);
}

const { passes, violations, incomplete, status } = this.state;

let actionTitle;
if (status === 'ready') {
Expand All @@ -162,7 +194,7 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
);
}

return active ? (
return (
<Fragment>
<Provider store={store}>
{status === 'running' ? (
Expand Down Expand Up @@ -218,6 +250,6 @@ export class A11YPanel extends Component<A11YPanelProps, A11YPanelState> {
/>
</Provider>
</Fragment>
) : null;
);
}
}
Expand Up @@ -177,6 +177,10 @@ exports[`A11YPanel should render report 1`] = `
"storybook/a11y/result",
[Function],
],
Array [
"storybook/a11y/error",
[Function],
],
],
"results": Array [
Object {
Expand All @@ -187,6 +191,10 @@ exports[`A11YPanel should render report 1`] = `
"type": "return",
"value": undefined,
},
Object {
"type": "return",
"value": undefined,
},
],
},
}
Expand Down
3 changes: 2 additions & 1 deletion addons/a11y/src/constants.ts
Expand Up @@ -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 };
13 changes: 11 additions & 2 deletions addons/a11y/src/index.ts
Expand Up @@ -39,20 +39,29 @@ 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)));
});
};

if (module && module.hot && module.hot.decline) {
module.hot.decline();
}

let storedDefaultSetup: Setup | null = null;

export const withA11y = makeDecorator({
name: 'withA11Y',
parameterName: PARAM_KEY,
wrapper: (getStory, context, { parameters }) => {
if (parameters) {
setup = parameters as Setup;
if (storedDefaultSetup === null) {
storedDefaultSetup = { ...setup };
donaldpipowitch marked this conversation as resolved.
Show resolved Hide resolved
}
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));

Expand Down