feedback and fixes

This commit is contained in:
Clément Dungler 2020-04-18 00:23:35 +02:00
parent 79e6df1d5f
commit 748503ad69
10 changed files with 113 additions and 89 deletions

View File

@ -46,13 +46,10 @@
"global": "^4.3.2",
"lodash": "^4.17.15",
"memoizerific": "^1.11.3",
"react-redux": "^7.0.2",
"react-sizeme": "^2.5.2",
"redux": "^4.0.1",
"regenerator-runtime": "^0.13.3"
},
"devDependencies": {
"@types/react-redux": "^7.0.6",
"@types/webpack-env": "^1.15.1",
"enzyme": "^3.11.0"
},

View File

@ -1,6 +1,6 @@
import { document } from 'global';
import addons from '@storybook/addons';
import { EVENTS } from './constants';
import { EVENTS, HIGHLIGHT_STYLE_ID } from './constants';
if (module && module.hot && module.hot.decline) {
module.hot.decline();
@ -15,7 +15,7 @@ interface HighlightInfo {
const channel = addons.getChannel();
const highlight = (infos: HighlightInfo) => {
const id = 'a11yHighlight';
const id = HIGHLIGHT_STYLE_ID;
const sheetToBeRemoved = document.getElementById(id);
if (sheetToBeRemoved) {
sheetToBeRemoved.parentNode.removeChild(sheetToBeRemoved);

View File

@ -0,0 +1,27 @@
import addons from '@storybook/addons';
import { STORY_RENDERED } from '@storybook/core-events';
import { EVENTS } from './constants';
jest.mock('@storybook/addons');
const mockedAddons = addons as jest.Mocked<typeof addons>;
describe('a11yRunner', () => {
let mockChannel: { on: jest.Mock; emit?: jest.Mock };
beforeEach(() => {
mockedAddons.getChannel.mockReset();
mockChannel = { on: jest.fn(), emit: jest.fn() };
mockedAddons.getChannel.mockReturnValue(mockChannel as any);
});
it('should listen to events', () => {
// eslint-disable-next-line global-require
require('./a11yRunner');
expect(mockedAddons.getChannel).toHaveBeenCalled();
expect(mockChannel.on).toHaveBeenCalledWith(STORY_RENDERED, expect.any(Function));
expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.REQUEST, expect.any(Function));
expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.MANUAL, expect.any(Function));
});
});

View File

@ -23,6 +23,8 @@ const run = async (storyId: string) => {
if (!active) {
active = true;
channel.emit(EVENTS.RUNNING);
const { element = getElement(), config, options } = input;
axe.reset();
if (config) {

View File

@ -48,10 +48,10 @@ const axeResult = {
],
};
function ThemedA11YPanel(props) {
function ThemedA11YPanel() {
return (
<ThemeProvider theme={convert(themes.light)}>
<A11YPanel {...props} />
<A11YPanel />
</ThemeProvider>
);
}
@ -61,10 +61,14 @@ describe('A11YPanel', () => {
mockedApi.useChannel.mockReset();
mockedApi.useParameter.mockReset();
mockedApi.useStorybookState.mockReset();
mockedApi.useAddonState.mockReset();
mockedApi.useChannel.mockReturnValue(jest.fn());
mockedApi.useParameter.mockReturnValue({ manual: false });
mockedApi.useStorybookState.mockReturnValue({ storyId: 'jest' });
const state: Partial<api.State> = { storyId: 'jest' };
// Lazy to mock entire state
mockedApi.useStorybookState.mockReturnValue(state as any);
mockedApi.useAddonState.mockImplementation(React.useState);
});
it('should render', () => {
@ -84,7 +88,6 @@ describe('A11YPanel', () => {
it('should handle "initial" status', () => {
const { getByText } = render(<A11YPanel />);
const text = getByText(/Initializing/);
expect(getByText(/Initializing/)).toBeTruthy();
});
@ -96,18 +99,29 @@ describe('A11YPanel', () => {
});
});
it('should handle "running" status', async () => {
const emit = jest.fn();
mockedApi.useChannel.mockReturnValue(emit);
mockedApi.useParameter.mockReturnValue({ manual: true });
const { getByRole, getByText } = render(<ThemedA11YPanel />);
await waitFor(() => {
const button = getByRole('button', { name: 'Run test' });
fireEvent.click(button);
describe('running', () => {
it('should handle "running" status', async () => {
const emit = jest.fn();
mockedApi.useChannel.mockReturnValue(emit);
mockedApi.useParameter.mockReturnValue({ manual: true });
const { getByRole, getByText } = render(<ThemedA11YPanel />);
await waitFor(() => {
const button = getByRole('button', { name: 'Run test' });
fireEvent.click(button);
});
await waitFor(() => {
expect(getByText(/Please wait while the accessibility scan is running/)).toBeTruthy();
expect(emit).toHaveBeenCalledWith(EVENTS.MANUAL, 'jest');
});
});
await waitFor(() => {
expect(getByText(/Please wait while the accessibility scan is running/)).toBeTruthy();
expect(emit).toHaveBeenCalledWith(EVENTS.MANUAL, 'jest');
it('should set running status on event', async () => {
const { getByText } = render(<ThemedA11YPanel />);
const useChannelArgs = mockedApi.useChannel.mock.calls[0][0];
act(() => useChannelArgs[EVENTS.RUNNING]());
await waitFor(() => {
expect(getByText(/Please wait while the accessibility scan is running/)).toBeTruthy();
});
});
});

View File

@ -1,16 +1,16 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { styled } from '@storybook/theming';
import { ActionBar, Icons, ScrollArea } from '@storybook/components';
import { AxeResults } from 'axe-core';
import { useChannel, useParameter, useStorybookState } from '@storybook/api';
import { useChannel, useParameter, useStorybookState, useAddonState } from '@storybook/api';
import { Report } from './Report';
import { Tabs } from './Tabs';
import { useA11yContext } from './A11yContext';
import { EVENTS } from '../constants';
import { EVENTS, ADDON_ID } from '../constants';
import { A11yParameters } from '../params';
export enum RuleType {
@ -51,7 +51,7 @@ const Centered = styled.span<{}>({
type Status = 'initial' | 'manual' | 'running' | 'error' | 'ran' | 'ready';
export const A11YPanel: React.FC = () => {
const [status, setStatus] = React.useState<Status>('initial');
const [status, setStatus] = useAddonState<Status>(ADDON_ID, 'initial');
const [error, setError] = React.useState<unknown>(undefined);
const { setResults, results } = useA11yContext();
const { passes, incomplete, violations } = results;
@ -75,12 +75,17 @@ export const A11YPanel: React.FC = () => {
}, 900);
};
const handleRun = useCallback(() => {
setStatus('running');
}, []);
const handleError = useCallback((err: unknown) => {
setStatus('error');
setError(err);
}, []);
const emit = useChannel({
[EVENTS.RUNNING]: handleRun,
[EVENTS.RESULT]: handleResult,
[EVENTS.ERROR]: handleError,
});
@ -90,13 +95,32 @@ export const A11YPanel: React.FC = () => {
emit(EVENTS.MANUAL, storyId);
}, [storyId]);
const manualActionItems = useMemo(() => [{ title: 'Run test', onClick: handleManual }], [
handleManual,
]);
const readyActionItems = useMemo(
() => [
{
title:
status === 'ready' ? (
'Rerun tests'
) : (
<>
<Icon inline icon="check" /> Tests completed
</>
),
onClick: handleManual,
},
],
[status, handleManual]
);
return (
<>
{status === 'initial' && <Centered>Initializing...</Centered>}
{status === 'manual' && (
<>
<Centered>Manually run the accessibility scan.</Centered>
<ActionBar key="actionbar" actionItems={[{ title: 'Run test', onClick: handleManual }]} />
<ActionBar key="actionbar" actionItems={manualActionItems} />
</>
)}
{status === 'running' && (
@ -150,29 +174,14 @@ export const A11YPanel: React.FC = () => {
]}
/>
</ScrollArea>
<ActionBar
key="actionbar"
actionItems={[
{
title:
status === 'ready' ? (
'Rerun tests'
) : (
<>
<Icon inline icon="check" /> Tests completed
</>
),
onClick: handleManual,
},
]}
/>
<ActionBar key="actionbar" actionItems={readyActionItems} />
</>
)}
{status === 'error' && (
<Centered>
The accessibility scan encountered an error.
<br />
{error}
{typeof error === 'string' ? error : JSON.stringify(error)}
</Centered>
)}
</>

View File

@ -1,6 +1,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { render, act } from '@testing-library/react';
import * as api from '@storybook/api';
import { STORY_CHANGED } from '@storybook/core-events';
import { A11yContextProvider } from './A11yContext';
import { EVENTS } from '../constants';
@ -54,4 +55,19 @@ describe('A11YPanel', () => {
})
);
});
it('should emit highlight with no values when story changed', () => {
const emit = jest.fn();
mockedApi.useChannel.mockReturnValue(emit);
render(<A11yContextProvider active />);
const useChannelArgs = mockedApi.useChannel.mock.calls[0][0];
act(() => useChannelArgs[STORY_CHANGED]());
expect(emit).toHaveBeenLastCalledWith(
EVENTS.HIGHLIGHT,
expect.objectContaining({
color: expect.any(String),
elements: [],
})
);
});
});

View File

@ -81,11 +81,7 @@ export const Item = (props: ItemProps) => {
{item.description}
</HeaderBar>
<HighlightToggleElement>
<HighlightToggle
toggleId={highlightToggleId}
type={type}
elementsToHighlight={item.nodes}
/>
<HighlightToggle toggleId={highlightToggleId} elementsToHighlight={item.nodes} />
</HighlightToggleElement>
</Wrapper>
{open ? (

View File

@ -2,12 +2,12 @@ export const ADDON_ID = 'storybook/a11y';
export const PANEL_ID = `${ADDON_ID}/panel`;
export const PARAM_KEY = `a11y`;
export const IFRAME = 'iframe';
export const ADD_ELEMENT = 'ADD_ELEMENT';
export const CLEAR_ELEMENTS = 'CLEAR_ELEMENTS';
export const HIGHLIGHT_STYLE_ID = 'a11yHighlight';
const RESULT = `${ADDON_ID}/result`;
const REQUEST = `${ADDON_ID}/request`;
const RUNNING = `${ADDON_ID}/running`;
const ERROR = `${ADDON_ID}/error`;
const MANUAL = `${ADDON_ID}/manual`;
const HIGHLIGHT = `${ADDON_ID}/highlight`;
export const EVENTS = { RESULT, REQUEST, ERROR, MANUAL, HIGHLIGHT };
export const EVENTS = { RESULT, REQUEST, RUNNING, ERROR, MANUAL, HIGHLIGHT };

View File

@ -4156,14 +4156,6 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860"
integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/html-minifier-terser@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz#7532440c138605ced1b555935c3115ddd20e8bef"
@ -4418,16 +4410,6 @@
dependencies:
"@types/react" "*"
"@types/react-redux@^7.0.6":
version "7.1.7"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a"
integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-select@^3.0.11":
version "3.0.11"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.11.tgz#b69b6fe1999bedfb05bd7499327206e16a7fb00e"
@ -24854,17 +24836,6 @@ react-popper@^1.3.7:
typed-styles "^0.0.7"
warning "^4.0.2"
react-redux@^7.0.2:
version "7.2.0"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
dependencies:
"@babel/runtime" "^7.5.5"
hoist-non-react-statics "^3.3.0"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.9.0"
react-scripts@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.0.1.tgz#e5565350d8069cc9966b5998d3fe3befe3d243ac"
@ -25394,14 +25365,6 @@ redeyed@~1.0.0:
dependencies:
esprima "~3.0.0"
redux@^4.0.0, redux@^4.0.1:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
reflect-metadata@^0.1.12, reflect-metadata@^0.1.2:
version "0.1.13"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@ -28317,7 +28280,7 @@ svgo@^1.0.0, svgo@^1.2.2, svgo@^1.3.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
symbol-observable@1.2.0, symbol-observable@^1.1.0, symbol-observable@^1.2.0:
symbol-observable@1.2.0, symbol-observable@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==