mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-02 05:03:44 +08:00
feedback and fixes
This commit is contained in:
parent
79e6df1d5f
commit
748503ad69
@ -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"
|
||||
},
|
||||
|
@ -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);
|
||||
|
27
addons/a11y/src/a11yRunner.test.ts
Normal file
27
addons/a11y/src/a11yRunner.test.ts
Normal 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));
|
||||
});
|
||||
});
|
@ -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) {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
@ -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: [],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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 ? (
|
||||
|
@ -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 };
|
||||
|
39
yarn.lock
39
yarn.lock
@ -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==
|
||||
|
Loading…
x
Reference in New Issue
Block a user