@techsio/storybook-better-a11y 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AccessibilityRuleMaps.js +532 -0
- package/dist/a11yRunner.js +105 -0
- package/dist/a11yRunner.test.js +21 -0
- package/dist/a11yRunnerUtils.js +30 -0
- package/dist/a11yRunnerUtils.test.js +61 -0
- package/dist/apcaChecker.js +288 -0
- package/dist/apcaChecker.test.js +124 -0
- package/dist/axeRuleMappingHelper.js +4 -0
- package/dist/components/A11YPanel.js +140 -0
- package/dist/components/A11YPanel.stories.js +198 -0
- package/dist/components/A11YPanel.test.js +110 -0
- package/dist/components/A11yContext.js +438 -0
- package/dist/components/A11yContext.test.js +277 -0
- package/dist/components/Report/Details.js +169 -0
- package/dist/components/Report/Report.js +106 -0
- package/dist/components/Report/Report.stories.js +86 -0
- package/dist/components/Tabs.js +54 -0
- package/dist/components/TestDiscrepancyMessage.js +55 -0
- package/dist/components/TestDiscrepancyMessage.stories.js +40 -0
- package/dist/components/VisionSimulator.js +83 -0
- package/dist/components/VisionSimulator.stories.js +56 -0
- package/dist/constants.js +25 -0
- package/dist/manager.test.js +86 -0
- package/dist/params.js +0 -0
- package/dist/preview.test.js +215 -0
- package/dist/results.mock.js +874 -0
- package/dist/types.js +6 -0
- package/dist/utils.js +21 -0
- package/dist/visionSimulatorFilters.js +100 -0
- package/dist/withVisionSimulator.js +41 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { Button, Tabs } from "storybook/internal/components";
|
|
3
|
+
import { CollapseIcon, ExpandAltIcon, EyeCloseIcon, EyeIcon, SyncIcon } from "@storybook/icons";
|
|
4
|
+
import { styled, useTheme } from "storybook/theming";
|
|
5
|
+
import { useA11yContext } from "./A11yContext.js";
|
|
6
|
+
const Container = styled.div({
|
|
7
|
+
width: '100%',
|
|
8
|
+
position: 'relative',
|
|
9
|
+
height: '100%',
|
|
10
|
+
overflow: 'hidden',
|
|
11
|
+
display: 'flex',
|
|
12
|
+
flexDirection: 'column'
|
|
13
|
+
});
|
|
14
|
+
const ActionsWrapper = styled.div({
|
|
15
|
+
display: 'flex',
|
|
16
|
+
justifyContent: 'flex-end',
|
|
17
|
+
gap: 6
|
|
18
|
+
});
|
|
19
|
+
const TabPanel = ({ id, children })=>/*#__PURE__*/ createElement("div", {
|
|
20
|
+
id: id
|
|
21
|
+
}, children);
|
|
22
|
+
const Tabs_Tabs = ({ tabs })=>{
|
|
23
|
+
const { tab, setTab, toggleHighlight, highlighted, handleManual, allExpanded, handleCollapseAll, handleExpandAll } = useA11yContext();
|
|
24
|
+
const theme = useTheme();
|
|
25
|
+
return /*#__PURE__*/ createElement(Container, null, /*#__PURE__*/ createElement(Tabs, {
|
|
26
|
+
backgroundColor: theme.background.app,
|
|
27
|
+
selected: tab,
|
|
28
|
+
actions: {
|
|
29
|
+
onSelect: (id)=>setTab(id)
|
|
30
|
+
},
|
|
31
|
+
tools: /*#__PURE__*/ createElement(ActionsWrapper, null, /*#__PURE__*/ createElement(Button, {
|
|
32
|
+
variant: "ghost",
|
|
33
|
+
padding: "small",
|
|
34
|
+
onClick: toggleHighlight,
|
|
35
|
+
ariaLabel: highlighted ? 'Hide accessibility test result highlights' : 'Highlight elements with accessibility test results'
|
|
36
|
+
}, highlighted ? /*#__PURE__*/ createElement(EyeCloseIcon, null) : /*#__PURE__*/ createElement(EyeIcon, null)), /*#__PURE__*/ createElement(Button, {
|
|
37
|
+
variant: "ghost",
|
|
38
|
+
padding: "small",
|
|
39
|
+
onClick: allExpanded ? handleCollapseAll : handleExpandAll,
|
|
40
|
+
ariaLabel: allExpanded ? 'Collapse all results' : 'Expand all results',
|
|
41
|
+
"aria-expanded": allExpanded
|
|
42
|
+
}, allExpanded ? /*#__PURE__*/ createElement(CollapseIcon, null) : /*#__PURE__*/ createElement(ExpandAltIcon, null)), /*#__PURE__*/ createElement(Button, {
|
|
43
|
+
variant: "ghost",
|
|
44
|
+
padding: "small",
|
|
45
|
+
onClick: handleManual,
|
|
46
|
+
ariaLabel: "Rerun accessibility scan"
|
|
47
|
+
}, /*#__PURE__*/ createElement(SyncIcon, null)))
|
|
48
|
+
}, tabs.map((tabItem)=>/*#__PURE__*/ createElement(TabPanel, {
|
|
49
|
+
key: tabItem.type,
|
|
50
|
+
id: tabItem.type,
|
|
51
|
+
title: tabItem.label
|
|
52
|
+
}, tabItem.panel))));
|
|
53
|
+
};
|
|
54
|
+
export { Tabs_Tabs as Tabs };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import react, { useMemo } from "react";
|
|
2
|
+
import { Link } from "storybook/internal/components";
|
|
3
|
+
import { useStorybookApi } from "storybook/manager-api";
|
|
4
|
+
import { styled } from "storybook/theming";
|
|
5
|
+
import { DOCUMENTATION_DISCREPANCY_LINK } from "../constants.js";
|
|
6
|
+
const Wrapper = styled.div(({ theme: { color, typography, background } })=>({
|
|
7
|
+
textAlign: 'start',
|
|
8
|
+
padding: '11px 15px',
|
|
9
|
+
fontSize: `${typography.size.s2}px`,
|
|
10
|
+
fontWeight: typography.weight.regular,
|
|
11
|
+
lineHeight: '1rem',
|
|
12
|
+
background: background.app,
|
|
13
|
+
borderBottom: `1px solid ${color.border}`,
|
|
14
|
+
color: color.defaultText,
|
|
15
|
+
backgroundClip: 'padding-box',
|
|
16
|
+
position: 'relative',
|
|
17
|
+
code: {
|
|
18
|
+
fontSize: `${typography.size.s1 - 1}px`,
|
|
19
|
+
color: 'inherit',
|
|
20
|
+
margin: '0 0.2em',
|
|
21
|
+
padding: '0 0.2em',
|
|
22
|
+
background: 'rgba(255, 255, 255, 0.8)',
|
|
23
|
+
borderRadius: '2px',
|
|
24
|
+
boxShadow: '0 0 0 1px rgba(0, 0, 0, 0.1)'
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
const TestDiscrepancyMessage = ({ discrepancy })=>{
|
|
28
|
+
const api = useStorybookApi();
|
|
29
|
+
const docsUrl = api.getDocsUrl({
|
|
30
|
+
subpath: DOCUMENTATION_DISCREPANCY_LINK,
|
|
31
|
+
versioned: true,
|
|
32
|
+
renderer: true
|
|
33
|
+
});
|
|
34
|
+
const message = useMemo(()=>{
|
|
35
|
+
switch(discrepancy){
|
|
36
|
+
case 'browserPassedCliFailed':
|
|
37
|
+
return 'Accessibility checks passed in this browser but failed in the CLI.';
|
|
38
|
+
case 'cliPassedBrowserFailed':
|
|
39
|
+
return 'Accessibility checks passed in the CLI but failed in this browser.';
|
|
40
|
+
case 'cliFailedButModeManual':
|
|
41
|
+
return 'Accessibility checks failed in the CLI. Run the tests manually to see the results.';
|
|
42
|
+
default:
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}, [
|
|
46
|
+
discrepancy
|
|
47
|
+
]);
|
|
48
|
+
if (!message) return null;
|
|
49
|
+
return /*#__PURE__*/ react.createElement(Wrapper, null, message, ' ', /*#__PURE__*/ react.createElement(Link, {
|
|
50
|
+
href: docsUrl,
|
|
51
|
+
target: "_blank",
|
|
52
|
+
withArrow: true
|
|
53
|
+
}, "Learn what could cause this"));
|
|
54
|
+
};
|
|
55
|
+
export { TestDiscrepancyMessage };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
import { ManagerContext } from "storybook/manager-api";
|
|
3
|
+
import { fn } from "storybook/test";
|
|
4
|
+
import preview from "../../../../.storybook/preview";
|
|
5
|
+
import { TestDiscrepancyMessage } from "./TestDiscrepancyMessage.js";
|
|
6
|
+
const managerContext = {
|
|
7
|
+
state: {},
|
|
8
|
+
api: {
|
|
9
|
+
getDocsUrl: fn().mockName('api::getDocsUrl'),
|
|
10
|
+
getCurrentParameter: fn().mockName('api::getCurrentParameter')
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const meta = preview.meta({
|
|
14
|
+
title: 'TestDiscrepancyMessage',
|
|
15
|
+
component: TestDiscrepancyMessage,
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen'
|
|
18
|
+
},
|
|
19
|
+
decorators: [
|
|
20
|
+
(storyFn)=>/*#__PURE__*/ react.createElement(ManagerContext.Provider, {
|
|
21
|
+
value: managerContext
|
|
22
|
+
}, storyFn())
|
|
23
|
+
]
|
|
24
|
+
});
|
|
25
|
+
const BrowserPassedCliFailed = meta.story({
|
|
26
|
+
args: {
|
|
27
|
+
discrepancy: 'browserPassedCliFailed'
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const CliPassedBrowserFailed = meta.story({
|
|
31
|
+
args: {
|
|
32
|
+
discrepancy: 'cliPassedBrowserFailed'
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const CliFailedButModeManual = meta.story({
|
|
36
|
+
args: {
|
|
37
|
+
discrepancy: 'cliFailedButModeManual'
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
export { BrowserPassedCliFailed, CliFailedButModeManual, CliPassedBrowserFailed };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import react from "react";
|
|
2
|
+
import { Button } from "storybook/internal/components";
|
|
3
|
+
import { AccessibilityIcon } from "@storybook/icons";
|
|
4
|
+
import { useGlobals } from "storybook/manager-api";
|
|
5
|
+
import { styled } from "storybook/theming";
|
|
6
|
+
import { VISION_GLOBAL_KEY } from "../constants.js";
|
|
7
|
+
import { filterDefs, filters } from "../visionSimulatorFilters.js";
|
|
8
|
+
const Hidden = styled.div({
|
|
9
|
+
'&, & svg': {
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
width: 0,
|
|
12
|
+
height: 0
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
const ColorIcon = styled.span({
|
|
16
|
+
background: 'linear-gradient(to right, #F44336, #FF9800, #FFEB3B, #8BC34A, #2196F3, #9C27B0)',
|
|
17
|
+
borderRadius: 14,
|
|
18
|
+
display: 'block',
|
|
19
|
+
flexShrink: 0,
|
|
20
|
+
height: 14,
|
|
21
|
+
width: 14
|
|
22
|
+
}, ({ $filter })=>({
|
|
23
|
+
filter: filters[$filter]?.filter || 'none'
|
|
24
|
+
}), ({ theme })=>({
|
|
25
|
+
boxShadow: `${theme.appBorderColor} 0 0 0 1px inset`
|
|
26
|
+
}));
|
|
27
|
+
const SelectRow = styled.div(({ theme })=>({
|
|
28
|
+
display: 'flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
gap: 8,
|
|
31
|
+
padding: '6px 10px',
|
|
32
|
+
border: `1px solid ${theme.appBorderColor}`,
|
|
33
|
+
borderRadius: theme.appBorderRadius,
|
|
34
|
+
background: theme.background.content
|
|
35
|
+
}));
|
|
36
|
+
const SelectInput = styled.select(({ theme })=>({
|
|
37
|
+
flex: 1,
|
|
38
|
+
minWidth: 0,
|
|
39
|
+
padding: '4px 6px',
|
|
40
|
+
border: `1px solid ${theme.appBorderColor}`,
|
|
41
|
+
borderRadius: theme.appBorderRadius,
|
|
42
|
+
background: theme.background.app,
|
|
43
|
+
color: theme.color.defaultText,
|
|
44
|
+
fontSize: theme.typography.size.s2
|
|
45
|
+
}));
|
|
46
|
+
const ResetButton = styled(Button)({
|
|
47
|
+
whiteSpace: 'nowrap'
|
|
48
|
+
});
|
|
49
|
+
const VisionSimulator = ()=>{
|
|
50
|
+
const [globals, updateGlobals] = useGlobals();
|
|
51
|
+
const value = globals[VISION_GLOBAL_KEY];
|
|
52
|
+
const options = Object.entries(filters).map(([key, { label, percentage }])=>({
|
|
53
|
+
label: percentage ? `${label} (${percentage}% of users)` : label,
|
|
54
|
+
value: key
|
|
55
|
+
}));
|
|
56
|
+
return /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(SelectRow, {
|
|
57
|
+
"aria-label": "Vision simulator"
|
|
58
|
+
}, /*#__PURE__*/ react.createElement(AccessibilityIcon, null), /*#__PURE__*/ react.createElement(ColorIcon, {
|
|
59
|
+
$filter: String(value || 'none')
|
|
60
|
+
}), /*#__PURE__*/ react.createElement(SelectInput, {
|
|
61
|
+
value: value ?? '',
|
|
62
|
+
onChange: (event)=>updateGlobals({
|
|
63
|
+
[VISION_GLOBAL_KEY]: event.target.value || void 0
|
|
64
|
+
})
|
|
65
|
+
}, /*#__PURE__*/ react.createElement("option", {
|
|
66
|
+
value: ""
|
|
67
|
+
}, "No filter"), options.map((option)=>/*#__PURE__*/ react.createElement("option", {
|
|
68
|
+
key: option.value,
|
|
69
|
+
value: option.value
|
|
70
|
+
}, option.label))), /*#__PURE__*/ react.createElement(ResetButton, {
|
|
71
|
+
variant: "ghost",
|
|
72
|
+
padding: "small",
|
|
73
|
+
onClick: ()=>updateGlobals({
|
|
74
|
+
[VISION_GLOBAL_KEY]: void 0
|
|
75
|
+
}),
|
|
76
|
+
ariaLabel: "Reset color filter"
|
|
77
|
+
}, "Reset")), /*#__PURE__*/ react.createElement(Hidden, {
|
|
78
|
+
dangerouslySetInnerHTML: {
|
|
79
|
+
__html: filterDefs
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
82
|
+
};
|
|
83
|
+
export { VisionSimulator };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ManagerContext } from "storybook/manager-api";
|
|
2
|
+
import { expect, fn, screen } from "storybook/test";
|
|
3
|
+
import preview from "../../../../.storybook/preview";
|
|
4
|
+
import { VisionSimulator } from "./VisionSimulator.js";
|
|
5
|
+
const managerContext = {
|
|
6
|
+
state: {},
|
|
7
|
+
api: {
|
|
8
|
+
getGlobals: fn(()=>({
|
|
9
|
+
vision: void 0
|
|
10
|
+
})),
|
|
11
|
+
updateGlobals: fn(),
|
|
12
|
+
getStoryGlobals: fn(()=>({
|
|
13
|
+
vision: void 0
|
|
14
|
+
})),
|
|
15
|
+
getUserGlobals: fn(()=>({
|
|
16
|
+
vision: void 0
|
|
17
|
+
}))
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const meta = preview.meta({
|
|
21
|
+
title: 'Vision Simulator',
|
|
22
|
+
component: VisionSimulator,
|
|
23
|
+
decorators: [
|
|
24
|
+
(Story)=>/*#__PURE__*/ React.createElement(ManagerContext.Provider, {
|
|
25
|
+
value: managerContext
|
|
26
|
+
}, /*#__PURE__*/ React.createElement(Story, null))
|
|
27
|
+
]
|
|
28
|
+
});
|
|
29
|
+
const VisionSimulator_stories = meta;
|
|
30
|
+
const openMenu = async ({ canvas, userEvent })=>{
|
|
31
|
+
await userEvent.click(canvas.getByRole('button', {
|
|
32
|
+
name: 'Vision simulator'
|
|
33
|
+
}));
|
|
34
|
+
};
|
|
35
|
+
const Default = meta.story({
|
|
36
|
+
play: openMenu
|
|
37
|
+
});
|
|
38
|
+
const WithFilter = meta.story({
|
|
39
|
+
play: openMenu,
|
|
40
|
+
globals: {
|
|
41
|
+
vision: 'achromatopsia'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const Selection = meta.story({
|
|
45
|
+
play: async (context)=>{
|
|
46
|
+
await openMenu(context);
|
|
47
|
+
await context.userEvent.click(await screen.findByText('Blurred vision'));
|
|
48
|
+
await expect(managerContext.api.updateGlobals).toHaveBeenCalledWith({
|
|
49
|
+
vision: 'blurred'
|
|
50
|
+
});
|
|
51
|
+
await expect(context.canvas.getByRole('button', {
|
|
52
|
+
name: 'Vision simulator Blurred vision'
|
|
53
|
+
})).toBeVisible();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
export { Default, Selection, WithFilter, VisionSimulator_stories as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const ADDON_ID = 'storybook/a11y';
|
|
2
|
+
const PANEL_ID = `${ADDON_ID}/panel`;
|
|
3
|
+
const PARAM_KEY = "a11y";
|
|
4
|
+
const VISION_GLOBAL_KEY = "vision";
|
|
5
|
+
const UI_STATE_ID = `${ADDON_ID}/ui`;
|
|
6
|
+
const RESULT = `${ADDON_ID}/result`;
|
|
7
|
+
const REQUEST = `${ADDON_ID}/request`;
|
|
8
|
+
const RUNNING = `${ADDON_ID}/running`;
|
|
9
|
+
const ERROR = `${ADDON_ID}/error`;
|
|
10
|
+
const MANUAL = `${ADDON_ID}/manual`;
|
|
11
|
+
const SELECT = `${ADDON_ID}/select`;
|
|
12
|
+
const DOCUMENTATION_LINK = 'writing-tests/accessibility-testing';
|
|
13
|
+
const DOCUMENTATION_DISCREPANCY_LINK = `${DOCUMENTATION_LINK}#why-are-my-tests-failing-in-different-environments`;
|
|
14
|
+
const TEST_PROVIDER_ID = 'storybook/addon-a11y/test-provider';
|
|
15
|
+
const EVENTS = {
|
|
16
|
+
RESULT,
|
|
17
|
+
REQUEST,
|
|
18
|
+
RUNNING,
|
|
19
|
+
ERROR,
|
|
20
|
+
MANUAL,
|
|
21
|
+
SELECT
|
|
22
|
+
};
|
|
23
|
+
const STATUS_TYPE_ID_COMPONENT_TEST = 'storybook/component-test';
|
|
24
|
+
const STATUS_TYPE_ID_A11Y = 'storybook/a11y';
|
|
25
|
+
export { ADDON_ID, DOCUMENTATION_DISCREPANCY_LINK, DOCUMENTATION_LINK, EVENTS, PANEL_ID, PARAM_KEY, STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, TEST_PROVIDER_ID, UI_STATE_ID, VISION_GLOBAL_KEY };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { PANEL_ID } from "./constants.js";
|
|
3
|
+
import "./manager.js";
|
|
4
|
+
import * as __rspack_external_storybook_manager_api_d834c1f5 from "storybook/manager-api";
|
|
5
|
+
vi.mock('storybook/manager-api');
|
|
6
|
+
const mockedApi = vi.mocked(__rspack_external_storybook_manager_api_d834c1f5);
|
|
7
|
+
mockedApi.useStorybookApi = vi.fn(()=>({
|
|
8
|
+
getSelectedPanel: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
mockedApi.useAddonState = vi.fn();
|
|
11
|
+
const mockedAddons = vi.mocked(__rspack_external_storybook_manager_api_d834c1f5.addons);
|
|
12
|
+
const registrationImpl = mockedAddons.register.mock.calls[0][1];
|
|
13
|
+
const isPanel = (input)=>input.type === __rspack_external_storybook_manager_api_d834c1f5.types.PANEL;
|
|
14
|
+
describe('A11yManager', ()=>{
|
|
15
|
+
it('should register the panels', ()=>{
|
|
16
|
+
registrationImpl(mockedApi);
|
|
17
|
+
expect(mockedAddons.add.mock.calls).toHaveLength(2);
|
|
18
|
+
expect(mockedAddons.add).toHaveBeenCalledWith(PANEL_ID, expect.anything());
|
|
19
|
+
const panel = mockedAddons.add.mock.calls.map(([_, def])=>def).find(({ type })=>type === __rspack_external_storybook_manager_api_d834c1f5.types.PANEL);
|
|
20
|
+
const tool = mockedAddons.add.mock.calls.map(([_, def])=>def).find(({ type })=>type === __rspack_external_storybook_manager_api_d834c1f5.types.TOOL);
|
|
21
|
+
expect(panel).toBeDefined();
|
|
22
|
+
expect(tool).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
it('should compute title with no issues', ()=>{
|
|
25
|
+
mockedApi.useAddonState.mockImplementation(()=>[
|
|
26
|
+
{
|
|
27
|
+
results: void 0
|
|
28
|
+
}
|
|
29
|
+
]);
|
|
30
|
+
registrationImpl(__rspack_external_storybook_manager_api_d834c1f5);
|
|
31
|
+
const title = mockedAddons.add.mock.calls.map(([_, def])=>def).find(isPanel)?.title;
|
|
32
|
+
expect(title()).toMatchInlineSnapshot(`
|
|
33
|
+
<div
|
|
34
|
+
style={
|
|
35
|
+
{
|
|
36
|
+
"alignItems": "center",
|
|
37
|
+
"display": "flex",
|
|
38
|
+
"gap": 6,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
>
|
|
42
|
+
<span>
|
|
43
|
+
Accessibility
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
`);
|
|
47
|
+
});
|
|
48
|
+
it('should compute title with issues', ()=>{
|
|
49
|
+
mockedApi.useAddonState.mockImplementation(()=>[
|
|
50
|
+
{
|
|
51
|
+
results: {
|
|
52
|
+
violations: [
|
|
53
|
+
{}
|
|
54
|
+
],
|
|
55
|
+
incomplete: [
|
|
56
|
+
{},
|
|
57
|
+
{}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]);
|
|
62
|
+
registrationImpl(mockedApi);
|
|
63
|
+
const title = mockedAddons.add.mock.calls.map(([_, def])=>def).find(isPanel)?.title;
|
|
64
|
+
expect(title()).toMatchInlineSnapshot(`
|
|
65
|
+
<div
|
|
66
|
+
style={
|
|
67
|
+
{
|
|
68
|
+
"alignItems": "center",
|
|
69
|
+
"display": "flex",
|
|
70
|
+
"gap": 6,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
<span>
|
|
75
|
+
Accessibility
|
|
76
|
+
</span>
|
|
77
|
+
<Badge
|
|
78
|
+
compact={true}
|
|
79
|
+
status="neutral"
|
|
80
|
+
>
|
|
81
|
+
3
|
|
82
|
+
</Badge>
|
|
83
|
+
</div>
|
|
84
|
+
`);
|
|
85
|
+
});
|
|
86
|
+
});
|
package/dist/params.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { run } from "./a11yRunner.js";
|
|
3
|
+
import { afterEach } from "./preview.js";
|
|
4
|
+
import { getIsVitestRunning, getIsVitestStandaloneRun } from "./utils.js";
|
|
5
|
+
const mocks = vi.hoisted(()=>({
|
|
6
|
+
getIsVitestRunning: vi.fn(),
|
|
7
|
+
getIsVitestStandaloneRun: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
vi.mock(import("./a11yRunner.js"));
|
|
10
|
+
vi.mock(import("./utils.js"), async (importOriginal)=>{
|
|
11
|
+
const mod = await importOriginal();
|
|
12
|
+
return {
|
|
13
|
+
...mod,
|
|
14
|
+
getIsVitestRunning: mocks.getIsVitestRunning,
|
|
15
|
+
getIsVitestStandaloneRun: mocks.getIsVitestStandaloneRun
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
const mockedRun = vi.mocked(run);
|
|
19
|
+
const violations = [
|
|
20
|
+
{
|
|
21
|
+
id: 'color-contrast',
|
|
22
|
+
impact: 'serious',
|
|
23
|
+
tags: '_duplicate_["args","0","reporters","0","result","passes","12","tags"]',
|
|
24
|
+
description: 'Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds',
|
|
25
|
+
help: 'Elements must meet minimum color contrast ratio thresholds',
|
|
26
|
+
helpUrl: 'https://dequeuniversity.com/rules/axe/4.8/color-contrast?application=axeAPI',
|
|
27
|
+
nodes: [
|
|
28
|
+
{
|
|
29
|
+
any: [
|
|
30
|
+
{
|
|
31
|
+
id: 'color-contrast',
|
|
32
|
+
data: {
|
|
33
|
+
fgColor: '#029cfd',
|
|
34
|
+
bgColor: '#f6f9fc',
|
|
35
|
+
contrastRatio: 2.76,
|
|
36
|
+
fontSize: '10.5pt (14px)',
|
|
37
|
+
fontWeight: 'normal',
|
|
38
|
+
messageKey: null,
|
|
39
|
+
expectedContrastRatio: '4.5:1',
|
|
40
|
+
shadowColor: '_undefined_'
|
|
41
|
+
},
|
|
42
|
+
relatedNodes: [
|
|
43
|
+
{
|
|
44
|
+
html: '<div class="css-1av19vu">',
|
|
45
|
+
target: [
|
|
46
|
+
'.css-1av19vu'
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
impact: 'serious',
|
|
51
|
+
message: 'Element has insufficient color contrast of 2.76 (foreground color: #029cfd, background color: #f6f9fc, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1',
|
|
52
|
+
'_constructor-name_': 'CheckResult'
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
all: [],
|
|
56
|
+
none: [],
|
|
57
|
+
impact: 'serious',
|
|
58
|
+
html: '<span class="css-1mjgzsp">',
|
|
59
|
+
target: [
|
|
60
|
+
'.css-1mjgzsp'
|
|
61
|
+
],
|
|
62
|
+
failureSummary: 'Fix any of the following:\n Element has insufficient color contrast of 2.76 (foreground color: #029cfd, background color: #f6f9fc, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1'
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
describe('afterEach', ()=>{
|
|
68
|
+
beforeEach(()=>{
|
|
69
|
+
vi.mocked(getIsVitestRunning).mockReturnValue(false);
|
|
70
|
+
vi.mocked(getIsVitestStandaloneRun).mockReturnValue(true);
|
|
71
|
+
});
|
|
72
|
+
const createContext = (overrides = {})=>({
|
|
73
|
+
viewMode: 'story',
|
|
74
|
+
reporting: {
|
|
75
|
+
reports: [],
|
|
76
|
+
addReport: vi.fn()
|
|
77
|
+
},
|
|
78
|
+
parameters: {
|
|
79
|
+
a11y: {
|
|
80
|
+
test: 'error'
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
globals: {
|
|
84
|
+
a11y: {}
|
|
85
|
+
},
|
|
86
|
+
...overrides
|
|
87
|
+
});
|
|
88
|
+
it('should run accessibility checks and report results', async ()=>{
|
|
89
|
+
const context = createContext();
|
|
90
|
+
const result = {
|
|
91
|
+
violations
|
|
92
|
+
};
|
|
93
|
+
mockedRun.mockResolvedValue(result);
|
|
94
|
+
await expect(()=>afterEach(context)).rejects.toThrow();
|
|
95
|
+
expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y, context.id);
|
|
96
|
+
expect(context.reporting.addReport).toHaveBeenCalledWith({
|
|
97
|
+
type: 'a11y',
|
|
98
|
+
version: 1,
|
|
99
|
+
result,
|
|
100
|
+
status: 'failed'
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
it('should run accessibility checks and report results without throwing', async ()=>{
|
|
104
|
+
const context = createContext();
|
|
105
|
+
const result = {
|
|
106
|
+
violations
|
|
107
|
+
};
|
|
108
|
+
mockedRun.mockResolvedValue(result);
|
|
109
|
+
mocks.getIsVitestStandaloneRun.mockReturnValue(false);
|
|
110
|
+
await afterEach(context);
|
|
111
|
+
expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y, context.id);
|
|
112
|
+
expect(context.reporting.addReport).toHaveBeenCalledWith({
|
|
113
|
+
type: 'a11y',
|
|
114
|
+
version: 1,
|
|
115
|
+
result,
|
|
116
|
+
status: 'failed'
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
it('should run accessibility checks and should report them as warnings', async ()=>{
|
|
120
|
+
const context = createContext({
|
|
121
|
+
parameters: {
|
|
122
|
+
a11y: {
|
|
123
|
+
test: 'todo'
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const result = {
|
|
128
|
+
violations
|
|
129
|
+
};
|
|
130
|
+
mockedRun.mockResolvedValue(result);
|
|
131
|
+
mocks.getIsVitestStandaloneRun.mockReturnValue(false);
|
|
132
|
+
await afterEach(context);
|
|
133
|
+
expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y, context.id);
|
|
134
|
+
expect(context.reporting.addReport).toHaveBeenCalledWith({
|
|
135
|
+
type: 'a11y',
|
|
136
|
+
version: 1,
|
|
137
|
+
result,
|
|
138
|
+
status: 'warning'
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
it('should report passed status when there are no violations', async ()=>{
|
|
142
|
+
const context = createContext();
|
|
143
|
+
const result = {
|
|
144
|
+
violations: []
|
|
145
|
+
};
|
|
146
|
+
mockedRun.mockResolvedValue(result);
|
|
147
|
+
await afterEach(context);
|
|
148
|
+
expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y, context.id);
|
|
149
|
+
expect(context.reporting.addReport).toHaveBeenCalledWith({
|
|
150
|
+
type: 'a11y',
|
|
151
|
+
version: 1,
|
|
152
|
+
result,
|
|
153
|
+
status: 'passed'
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
it('should not run accessibility checks when disable is true', async ()=>{
|
|
157
|
+
const context = createContext({
|
|
158
|
+
parameters: {
|
|
159
|
+
a11y: {
|
|
160
|
+
disable: true
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
await afterEach(context);
|
|
165
|
+
expect(mockedRun).not.toHaveBeenCalled();
|
|
166
|
+
expect(context.reporting.addReport).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
it('should not run accessibility checks when globals manual is true', async ()=>{
|
|
169
|
+
const context = createContext({
|
|
170
|
+
globals: {
|
|
171
|
+
a11y: {
|
|
172
|
+
manual: true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
await afterEach(context);
|
|
177
|
+
expect(mockedRun).not.toHaveBeenCalled();
|
|
178
|
+
expect(context.reporting.addReport).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
it('should not run accessibility checks when parameters.a11y.test is "off"', async ()=>{
|
|
181
|
+
const context = createContext({
|
|
182
|
+
parameters: {
|
|
183
|
+
a11y: {
|
|
184
|
+
test: 'off'
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
await afterEach(context);
|
|
189
|
+
expect(mockedRun).not.toHaveBeenCalled();
|
|
190
|
+
expect(context.reporting.addReport).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
it('should report error when run throws an error', async ()=>{
|
|
193
|
+
const context = createContext();
|
|
194
|
+
const error = new Error('Test error');
|
|
195
|
+
mockedRun.mockRejectedValue(error);
|
|
196
|
+
await expect(()=>afterEach(context)).rejects.toThrow();
|
|
197
|
+
expect(mockedRun).toHaveBeenCalledWith(context.parameters.a11y, context.id);
|
|
198
|
+
expect(context.reporting.addReport).toHaveBeenCalledWith({
|
|
199
|
+
type: 'a11y',
|
|
200
|
+
version: 1,
|
|
201
|
+
result: {
|
|
202
|
+
error
|
|
203
|
+
},
|
|
204
|
+
status: 'failed'
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
it('should not run in docs mode', async ()=>{
|
|
208
|
+
const context = createContext({
|
|
209
|
+
viewMode: 'docs'
|
|
210
|
+
});
|
|
211
|
+
await afterEach(context);
|
|
212
|
+
expect(mockedRun).not.toHaveBeenCalled();
|
|
213
|
+
expect(context.reporting.addReport).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
});
|