@accesslint/storybook-addon 0.6.9 → 0.8.0
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/README.md +225 -24
- package/dist/manager.js +100 -22
- package/dist/matchers.cjs +14 -0
- package/dist/matchers.d.cts +1 -0
- package/dist/matchers.d.ts +1 -0
- package/dist/matchers.js +1 -0
- package/dist/portable.cjs +80 -0
- package/dist/portable.d.cts +46 -0
- package/dist/portable.d.ts +46 -0
- package/dist/portable.js +77 -0
- package/dist/preview.cjs +5 -1
- package/dist/preview.d.cts +2 -1
- package/dist/preview.d.ts +2 -1
- package/dist/preview.js +5 -1
- package/dist/vitest-plugin.cjs +8 -2
- package/dist/vitest-plugin.d.cts +14 -2
- package/dist/vitest-plugin.d.ts +14 -2
- package/dist/vitest-plugin.js +8 -2
- package/dist/vitest-setup.cjs +10 -3
- package/dist/vitest-setup.d.cts +2 -0
- package/dist/vitest-setup.d.ts +2 -0
- package/dist/vitest-setup.js +10 -3
- package/matchers.d.ts +10 -0
- package/package.json +15 -4
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ Catch accessibility violations in your Storybook stories as you develop. Powered
|
|
|
7
7
|
|
|
8
8
|
<img width="637" height="414" alt="Storybook screenshot with alt text violation in the details of the AccessLint tab" src="https://github.com/user-attachments/assets/01d2de92-0769-4564-8971-f6edc1986010" />
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
## Getting Started
|
|
12
11
|
|
|
13
12
|
```sh
|
|
@@ -18,52 +17,138 @@ Add the addon to your `.storybook/main.ts` (or `.storybook/main.js`):
|
|
|
18
17
|
|
|
19
18
|
```ts
|
|
20
19
|
const config = {
|
|
21
|
-
addons: ["@
|
|
20
|
+
addons: ["@accesslint/storybook-addon"],
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
export default config;
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
Restart Storybook and an **AccessLint** panel will appear in the addon bar. Every story is audited automatically after it renders.
|
|
27
|
+
|
|
28
|
+
## Vitest integration
|
|
29
|
+
|
|
30
|
+
If you use [`@storybook/addon-vitest`](https://storybook.js.org/docs/writing-tests/vitest-plugin), add the AccessLint plugin next to `storybookTest()` in your Vite config:
|
|
28
31
|
|
|
29
32
|
```ts
|
|
30
|
-
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
31
33
|
import { accesslintTest } from "@accesslint/storybook-addon/vitest-plugin";
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
// Inside your Storybook test project:
|
|
36
|
+
plugins: [
|
|
37
|
+
storybookTest({ configDir: ".storybook" }),
|
|
38
|
+
accesslintTest(),
|
|
39
|
+
],
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This gives you:
|
|
43
|
+
|
|
44
|
+
- Per-story status dots in the sidebar (green/yellow/red)
|
|
45
|
+
- A test widget in the sidebar's testing module
|
|
46
|
+
- The `toBeAccessible()` matcher registered automatically
|
|
47
|
+
- Accessibility results in CI alongside your component tests
|
|
48
|
+
|
|
49
|
+
## Accessibility assertions
|
|
50
|
+
|
|
51
|
+
Use `toBeAccessible()` to make accessibility a first-class assertion in your tests and play functions.
|
|
52
|
+
|
|
53
|
+
### With the Vitest plugin
|
|
54
|
+
|
|
55
|
+
If you added `accesslintTest()` above, the matcher is already registered. Use it directly in play functions:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { expect } from "storybook/test";
|
|
59
|
+
|
|
60
|
+
export const Default = {
|
|
61
|
+
play: async ({ canvasElement }) => {
|
|
62
|
+
await expect(canvasElement).toBeAccessible();
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Without the Vitest plugin
|
|
68
|
+
|
|
69
|
+
For play functions or standalone tests without the plugin, import the matchers entry point to register `toBeAccessible()`:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import "@accesslint/storybook-addon/matchers";
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Then use it in a play function:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { expect } from "storybook/test";
|
|
79
|
+
import "@accesslint/storybook-addon/matchers";
|
|
80
|
+
|
|
81
|
+
export const Default = {
|
|
82
|
+
play: async ({ canvasElement }) => {
|
|
83
|
+
await expect(canvasElement).toBeAccessible();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or in a standalone Vitest/Jest test:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import "@accesslint/storybook-addon/matchers";
|
|
92
|
+
import { render } from "@testing-library/react";
|
|
93
|
+
|
|
94
|
+
test("LoginForm is accessible", () => {
|
|
95
|
+
const { container } = render(<LoginForm />);
|
|
96
|
+
expect(container).toBeAccessible();
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Disabling rules per assertion
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
await expect(canvasElement).toBeAccessible({
|
|
104
|
+
disabledRules: ["accesslint-045"],
|
|
38
105
|
});
|
|
39
106
|
```
|
|
40
107
|
|
|
41
|
-
|
|
108
|
+
### Failure output
|
|
42
109
|
|
|
43
|
-
|
|
110
|
+
When the assertion fails, the error message lists each violation with its rule ID, WCAG criteria, conformance level, message, and the CSS selector of the failing element:
|
|
44
111
|
|
|
45
|
-
|
|
112
|
+
```
|
|
113
|
+
Expected element to have no accessibility violations, but found 2:
|
|
114
|
+
|
|
115
|
+
accesslint-001 [A] (1.1.1): Image is missing alt text
|
|
116
|
+
img[src="hero.png"]
|
|
117
|
+
|
|
118
|
+
accesslint-012 [A] (1.3.1): Form input is missing a label
|
|
119
|
+
input[type="email"]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### TypeScript support
|
|
123
|
+
|
|
124
|
+
Add the type reference to your `tsconfig.json`:
|
|
46
125
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"compilerOptions": {
|
|
129
|
+
"types": ["@accesslint/storybook-addon/matchers"]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or add a triple-slash reference in a `.d.ts` file:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
/// <reference types="@accesslint/storybook-addon/matchers" />
|
|
138
|
+
```
|
|
51
139
|
|
|
52
140
|
## Configuration
|
|
53
141
|
|
|
54
|
-
###
|
|
142
|
+
### Test mode
|
|
55
143
|
|
|
56
|
-
Control
|
|
144
|
+
Control how violations are reported via `parameters.accesslint`:
|
|
57
145
|
|
|
58
146
|
```ts
|
|
59
|
-
// .storybook/preview.ts
|
|
147
|
+
// .storybook/preview.ts — applies to all stories
|
|
60
148
|
const preview = {
|
|
61
149
|
parameters: {
|
|
62
150
|
accesslint: {
|
|
63
|
-
|
|
64
|
-
// 'error' - fail CI on violations
|
|
65
|
-
// 'off' - skip checks entirely
|
|
66
|
-
test: "todo",
|
|
151
|
+
test: "todo", // "error" (default) | "todo" | "off"
|
|
67
152
|
},
|
|
68
153
|
},
|
|
69
154
|
};
|
|
@@ -71,9 +156,25 @@ const preview = {
|
|
|
71
156
|
export default preview;
|
|
72
157
|
```
|
|
73
158
|
|
|
159
|
+
| Mode | Behavior |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `"error"` | Violations fail the test (default) |
|
|
162
|
+
| `"todo"` | Violations show as warnings — yellow sidebar dots, non-blocking in CI |
|
|
163
|
+
| `"off"` | Skip auditing entirely |
|
|
164
|
+
|
|
165
|
+
Override per-story:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
export const Experimental = {
|
|
169
|
+
parameters: {
|
|
170
|
+
accesslint: { test: "off" },
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
74
175
|
### Disabling rules
|
|
75
176
|
|
|
76
|
-
Disable specific rules in your preview file:
|
|
177
|
+
Disable specific rules globally in your preview file:
|
|
77
178
|
|
|
78
179
|
```ts
|
|
79
180
|
// .storybook/preview.ts
|
|
@@ -84,10 +185,110 @@ configureRules({
|
|
|
84
185
|
});
|
|
85
186
|
```
|
|
86
187
|
|
|
188
|
+
### Skipping stories with tags
|
|
189
|
+
|
|
190
|
+
Tag individual stories or entire components with `"no-a11y"` to skip auditing:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
// Skip a single story
|
|
194
|
+
export const Prototype = {
|
|
195
|
+
tags: ["no-a11y"],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Skip all stories for a component
|
|
199
|
+
export default {
|
|
200
|
+
component: ExperimentalWidget,
|
|
201
|
+
tags: ["no-a11y"],
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
With the Vitest plugin, you can also define custom skip tags:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
accesslintTest({
|
|
209
|
+
tags: { skip: ["no-a11y", "wip"] },
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Portable stories
|
|
214
|
+
|
|
215
|
+
Use AccessLint with [`composeStories`](https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest) outside of Storybook (plain Vitest, Jest, or Playwright CT).
|
|
216
|
+
|
|
217
|
+
In your test setup file, pass the AccessLint annotations to `setProjectAnnotations`:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
// vitest.setup.ts
|
|
221
|
+
import { setProjectAnnotations } from "@storybook/react";
|
|
222
|
+
import { enableAccessLint } from "@accesslint/storybook-addon/portable";
|
|
223
|
+
import * as previewAnnotations from "./.storybook/preview";
|
|
224
|
+
|
|
225
|
+
const project = setProjectAnnotations([
|
|
226
|
+
previewAnnotations,
|
|
227
|
+
enableAccessLint(),
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
beforeAll(project.beforeAll);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Then in your tests:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { composeStories } from "@storybook/react";
|
|
237
|
+
import * as stories from "./Button.stories";
|
|
238
|
+
|
|
239
|
+
const { Primary } = composeStories(stories);
|
|
240
|
+
|
|
241
|
+
test("Primary button is accessible", async () => {
|
|
242
|
+
await Primary.run();
|
|
243
|
+
// AccessLint afterEach runs automatically via the annotations
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## API reference
|
|
248
|
+
|
|
249
|
+
### Exports
|
|
250
|
+
|
|
251
|
+
| Entry point | Description |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `@accesslint/storybook-addon` | Main addon registration (manager + preview) |
|
|
254
|
+
| `@accesslint/storybook-addon/vitest-plugin` | `accesslintTest()` Vite plugin for Vitest integration |
|
|
255
|
+
| `@accesslint/storybook-addon/vitest-setup` | Setup file registered by the Vite plugin |
|
|
256
|
+
| `@accesslint/storybook-addon/matchers` | `toBeAccessible()` custom matcher |
|
|
257
|
+
| `@accesslint/storybook-addon/portable` | `enableAccessLint()` for portable stories |
|
|
258
|
+
| `@accesslint/storybook-addon/preview` | Preview annotations (afterEach hook) |
|
|
259
|
+
|
|
260
|
+
### `accesslintTest(options?)`
|
|
261
|
+
|
|
262
|
+
Vite plugin that registers AccessLint's `afterEach` annotation and the `toBeAccessible()` matcher for Vitest story tests.
|
|
263
|
+
|
|
264
|
+
| Option | Type | Description |
|
|
265
|
+
| --- | --- | --- |
|
|
266
|
+
| `tags.skip` | `string[]` | Stories with any of these tags will not be audited |
|
|
267
|
+
|
|
268
|
+
### `parameters.accesslint`
|
|
269
|
+
|
|
270
|
+
| Parameter | Type | Default | Description |
|
|
271
|
+
| --- | --- | --- | --- |
|
|
272
|
+
| `test` | `"todo" \| "error" \| "off"` | `"error"` | Controls how violations are reported |
|
|
273
|
+
| `disable` | `boolean` | `false` | Set to `true` to skip auditing (same as `test: "off"`) |
|
|
274
|
+
|
|
275
|
+
### `toBeAccessible(options?)`
|
|
276
|
+
|
|
277
|
+
Custom matcher for asserting an element has no accessibility violations.
|
|
278
|
+
|
|
279
|
+
| Option | Type | Description |
|
|
280
|
+
| --- | --- | --- |
|
|
281
|
+
| `disabledRules` | `string[]` | Rule IDs to skip for this assertion |
|
|
282
|
+
|
|
283
|
+
### `enableAccessLint()`
|
|
284
|
+
|
|
285
|
+
Returns AccessLint's preview annotations for use with `setProjectAnnotations` in portable stories setups.
|
|
286
|
+
|
|
87
287
|
## Compatibility
|
|
88
288
|
|
|
89
289
|
| Addon version | Storybook version |
|
|
90
290
|
| ------------- | ----------------- |
|
|
291
|
+
| 0.7.x | 10.x |
|
|
91
292
|
| 0.6.x | 10.x |
|
|
92
293
|
|
|
93
294
|
## Issues
|
package/dist/manager.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import React, { useMemo, useState, useRef, useCallback } from 'react';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import * as managerApi from 'storybook/internal/manager-api';
|
|
3
|
+
import { useChannel } from 'storybook/internal/manager-api';
|
|
4
4
|
import { styled, useTheme } from 'storybook/internal/theming';
|
|
5
5
|
import { STORY_CHANGED, STORY_FINISHED } from 'storybook/internal/core-events';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
8
13
|
|
|
9
14
|
// src/constants.ts
|
|
10
15
|
var ADDON_ID = "accesslint/a11y";
|
|
11
16
|
var PARAM_KEY = "accesslint";
|
|
17
|
+
var STATUS_TYPE_ID = "accesslint/a11y";
|
|
12
18
|
var IMPACT_COLOR = {
|
|
13
19
|
critical: "#d32f2f",
|
|
14
20
|
serious: "#d32f2f",
|
|
@@ -193,11 +199,31 @@ var Panel = ({ active }) => {
|
|
|
193
199
|
};
|
|
194
200
|
|
|
195
201
|
// src/manager.tsx
|
|
202
|
+
var { addons, types, useChannel: useChannel2, useStorybookApi } = managerApi;
|
|
196
203
|
var PANEL_ID = `${ADDON_ID}/panel`;
|
|
197
204
|
var TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`;
|
|
205
|
+
var _getStatusStore = managerApi.experimental_getStatusStore;
|
|
206
|
+
var _getTestProviderStore = managerApi.experimental_getTestProviderStore;
|
|
207
|
+
var _useTestProviderStore = managerApi.experimental_useTestProviderStore ?? null;
|
|
208
|
+
var hasTestProvider = !!(_getStatusStore && _getTestProviderStore);
|
|
209
|
+
var statusStore = hasTestProvider ? _getStatusStore(STATUS_TYPE_ID) : null;
|
|
210
|
+
var testProviderStore = hasTestProvider ? _getTestProviderStore(TEST_PROVIDER_ID) : null;
|
|
211
|
+
if (testProviderStore && statusStore) {
|
|
212
|
+
testProviderStore.onClearAll(() => {
|
|
213
|
+
statusStore.unset();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
var ActionList = null;
|
|
217
|
+
var Form = null;
|
|
218
|
+
try {
|
|
219
|
+
const components = __require("storybook/internal/components");
|
|
220
|
+
ActionList = components.ActionList ?? null;
|
|
221
|
+
Form = components.Form ?? null;
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
198
224
|
var Title = () => {
|
|
199
225
|
const [count, setCount] = React.useState(0);
|
|
200
|
-
|
|
226
|
+
useChannel2({
|
|
201
227
|
[STORY_FINISHED]: ({ reporters }) => {
|
|
202
228
|
const report = reporters.find((r) => r.type === "accesslint");
|
|
203
229
|
const violations = report?.result?.violations;
|
|
@@ -218,9 +244,7 @@ var Title = () => {
|
|
|
218
244
|
color: "inherit"
|
|
219
245
|
} }, /* @__PURE__ */ React.createElement("span", { style: { color: "#fff" } }, count)));
|
|
220
246
|
};
|
|
221
|
-
var StyledActionList = styled(ActionList)({
|
|
222
|
-
padding: 0
|
|
223
|
-
});
|
|
247
|
+
var StyledActionList = ActionList ? styled(ActionList)({ padding: 0 }) : styled.div({ padding: 0 });
|
|
224
248
|
var StatusDot = styled.div(
|
|
225
249
|
{
|
|
226
250
|
width: 6,
|
|
@@ -242,12 +266,36 @@ var StatusDot = styled.div(
|
|
|
242
266
|
var TestProviderWidget = () => {
|
|
243
267
|
const [violationCount, setViolationCount] = React.useState(null);
|
|
244
268
|
const api = useStorybookApi();
|
|
245
|
-
|
|
246
|
-
|
|
269
|
+
const providerState = _useTestProviderStore ? _useTestProviderStore((state) => state[TEST_PROVIDER_ID]) : null;
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
if (!statusStore) return;
|
|
272
|
+
const unsub = statusStore.onSelect(() => {
|
|
273
|
+
api.setSelectedPanel(PANEL_ID);
|
|
274
|
+
api.togglePanel(true);
|
|
275
|
+
});
|
|
276
|
+
return unsub;
|
|
277
|
+
}, [api]);
|
|
278
|
+
useChannel2({
|
|
279
|
+
[STORY_FINISHED]: ({ storyId, reporters }) => {
|
|
247
280
|
const report = reporters.find((r) => r.type === "accesslint");
|
|
248
281
|
if (!report) return;
|
|
249
|
-
const violations = report.result?.violations;
|
|
250
|
-
setViolationCount(violations
|
|
282
|
+
const violations = report.result?.violations ?? [];
|
|
283
|
+
setViolationCount(violations.length);
|
|
284
|
+
if (statusStore) {
|
|
285
|
+
const hasViolations2 = violations.length > 0;
|
|
286
|
+
const isWarning = report.status === "warning";
|
|
287
|
+
statusStore.set([{
|
|
288
|
+
value: hasViolations2 ? isWarning ? "status-value:warning" : "status-value:error" : "status-value:success",
|
|
289
|
+
typeId: STATUS_TYPE_ID,
|
|
290
|
+
storyId,
|
|
291
|
+
title: "AccessLint",
|
|
292
|
+
description: hasViolations2 ? `${violations.length} violation${violations.length === 1 ? "" : "s"}` : "No violations",
|
|
293
|
+
sidebarContextMenu: true
|
|
294
|
+
}]);
|
|
295
|
+
}
|
|
296
|
+
if (testProviderStore && providerState === "test-provider-state:running") {
|
|
297
|
+
testProviderStore.setState("test-provider-state:succeeded");
|
|
298
|
+
}
|
|
251
299
|
}
|
|
252
300
|
});
|
|
253
301
|
const hasViolations = violationCount !== null && violationCount > 0;
|
|
@@ -256,16 +304,43 @@ var TestProviderWidget = () => {
|
|
|
256
304
|
api.setSelectedPanel(PANEL_ID);
|
|
257
305
|
api.togglePanel(true);
|
|
258
306
|
};
|
|
259
|
-
|
|
260
|
-
ActionList.
|
|
307
|
+
if (ActionList && Form) {
|
|
308
|
+
return /* @__PURE__ */ React.createElement(StyledActionList, null, /* @__PURE__ */ React.createElement(ActionList.Item, null, /* @__PURE__ */ React.createElement(ActionList.Action, { as: "label", readOnly: true }, /* @__PURE__ */ React.createElement(ActionList.Icon, null, /* @__PURE__ */ React.createElement(Form.Checkbox, { name: "AccessLint", checked: true, disabled: true })), /* @__PURE__ */ React.createElement(ActionList.Text, null, "AccessLint")), /* @__PURE__ */ React.createElement(
|
|
309
|
+
ActionList.Button,
|
|
310
|
+
{
|
|
311
|
+
ariaLabel: violationCount === null ? "AccessLint: not run yet" : hasViolations ? `AccessLint: ${violationCount} violation${violationCount === 1 ? "" : "s"}` : "AccessLint: no violations",
|
|
312
|
+
disabled: violationCount === null,
|
|
313
|
+
onClick: openPanel
|
|
314
|
+
},
|
|
315
|
+
hasViolations ? violationCount : null,
|
|
316
|
+
/* @__PURE__ */ React.createElement(StatusDot, { status })
|
|
317
|
+
)));
|
|
318
|
+
}
|
|
319
|
+
return /* @__PURE__ */ React.createElement(
|
|
320
|
+
StyledActionList,
|
|
261
321
|
{
|
|
262
|
-
|
|
263
|
-
disabled: violationCount === null,
|
|
322
|
+
style: { display: "flex", alignItems: "center", gap: 8, padding: "6px 8px", cursor: "pointer" },
|
|
264
323
|
onClick: openPanel
|
|
265
324
|
},
|
|
266
|
-
|
|
267
|
-
/* @__PURE__ */ React.createElement(
|
|
268
|
-
|
|
325
|
+
/* @__PURE__ */ React.createElement(StatusDot, { status }),
|
|
326
|
+
/* @__PURE__ */ React.createElement("span", { style: { fontSize: 12 } }, "AccessLint"),
|
|
327
|
+
hasViolations && /* @__PURE__ */ React.createElement("span", { style: { fontSize: 11, fontWeight: "bold" } }, violationCount)
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
var SidebarContextMenu = ({ context }) => {
|
|
331
|
+
const api = useStorybookApi();
|
|
332
|
+
if (context.type !== "story" || !ActionList) return null;
|
|
333
|
+
return /* @__PURE__ */ React.createElement(
|
|
334
|
+
ActionList.Item,
|
|
335
|
+
{
|
|
336
|
+
onClick: () => {
|
|
337
|
+
api.selectStory(context.id);
|
|
338
|
+
api.setSelectedPanel(PANEL_ID);
|
|
339
|
+
api.togglePanel(true);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
/* @__PURE__ */ React.createElement(ActionList.Text, null, "View AccessLint results")
|
|
343
|
+
);
|
|
269
344
|
};
|
|
270
345
|
addons.register(ADDON_ID, () => {
|
|
271
346
|
addons.add(PANEL_ID, {
|
|
@@ -274,8 +349,11 @@ addons.register(ADDON_ID, () => {
|
|
|
274
349
|
render: Panel,
|
|
275
350
|
paramKey: PARAM_KEY
|
|
276
351
|
});
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
352
|
+
if (hasTestProvider && types.experimental_TEST_PROVIDER) {
|
|
353
|
+
addons.add(TEST_PROVIDER_ID, {
|
|
354
|
+
type: types.experimental_TEST_PROVIDER,
|
|
355
|
+
render: () => /* @__PURE__ */ React.createElement(TestProviderWidget, null),
|
|
356
|
+
sidebarContextMenu: ({ context }) => /* @__PURE__ */ React.createElement(SidebarContextMenu, { context })
|
|
357
|
+
});
|
|
358
|
+
}
|
|
281
359
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var matchers = require('@accesslint/vitest/matchers');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "accesslintMatchers", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return matchers.accesslintMatchers; }
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(exports, "toBeAccessible", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return matchers.toBeAccessible; }
|
|
14
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AccessibleMatcherOptions, accesslintMatchers, toBeAccessible } from '@accesslint/vitest/matchers';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AccessibleMatcherOptions, accesslintMatchers, toBeAccessible } from '@accesslint/vitest/matchers';
|
package/dist/matchers.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { accesslintMatchers, toBeAccessible } from '@accesslint/vitest/matchers';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@accesslint/core');
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/preview.ts
|
|
12
|
+
var preview_exports = {};
|
|
13
|
+
__export(preview_exports, {
|
|
14
|
+
afterEach: () => afterEach
|
|
15
|
+
});
|
|
16
|
+
core.configureRules({
|
|
17
|
+
disabledRules: ["accesslint-045"]
|
|
18
|
+
});
|
|
19
|
+
function scopeViolations(violations) {
|
|
20
|
+
const root = document.getElementById("storybook-root");
|
|
21
|
+
if (!root) return violations;
|
|
22
|
+
return violations.filter((v) => {
|
|
23
|
+
const local = v.selector.replace(/^.*>>>\s*iframe>\s*/, "");
|
|
24
|
+
try {
|
|
25
|
+
const el = document.querySelector(local);
|
|
26
|
+
return el && root.contains(el);
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function enrichViolations(violations) {
|
|
33
|
+
return violations.map((v) => {
|
|
34
|
+
const rule = core.getRuleById(v.ruleId);
|
|
35
|
+
return {
|
|
36
|
+
...v,
|
|
37
|
+
element: void 0,
|
|
38
|
+
// not serializable
|
|
39
|
+
description: rule?.description,
|
|
40
|
+
wcag: rule?.wcag,
|
|
41
|
+
level: rule?.level,
|
|
42
|
+
guidance: rule?.guidance
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
var afterEach = async ({
|
|
47
|
+
reporting,
|
|
48
|
+
parameters,
|
|
49
|
+
viewMode,
|
|
50
|
+
tags
|
|
51
|
+
}) => {
|
|
52
|
+
const accesslintParam = parameters?.accesslint;
|
|
53
|
+
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
54
|
+
if (viewMode !== "story") return;
|
|
55
|
+
if (tags?.includes("no-a11y")) return;
|
|
56
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
57
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
58
|
+
const result = core.runAudit(document);
|
|
59
|
+
const scoped = scopeViolations(result.violations);
|
|
60
|
+
const enriched = enrichViolations(scoped);
|
|
61
|
+
const hasViolations = enriched.length > 0;
|
|
62
|
+
const mode = accesslintParam?.test === "todo" ? "warning" : "failed";
|
|
63
|
+
reporting.addReport({
|
|
64
|
+
type: "accesslint",
|
|
65
|
+
version: 1,
|
|
66
|
+
result: {
|
|
67
|
+
violations: enriched,
|
|
68
|
+
ruleCount: result.ruleCount
|
|
69
|
+
},
|
|
70
|
+
status: hasViolations ? mode : "passed"
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/portable.ts
|
|
75
|
+
function enableAccessLint() {
|
|
76
|
+
return preview_exports;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
exports.accesslintAnnotations = preview_exports;
|
|
80
|
+
exports.enableAccessLint = enableAccessLint;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
declare const afterEach: ({ reporting, parameters, viewMode, tags, }: {
|
|
2
|
+
reporting: {
|
|
3
|
+
addReport: (report: Record<string, unknown>) => void;
|
|
4
|
+
};
|
|
5
|
+
parameters: Record<string, unknown>;
|
|
6
|
+
viewMode: string;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
|
|
10
|
+
declare const accesslintAnnotations_afterEach: typeof afterEach;
|
|
11
|
+
declare namespace accesslintAnnotations {
|
|
12
|
+
export { accesslintAnnotations_afterEach as afterEach };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Portable stories helper for using AccessLint with composeStories
|
|
17
|
+
* outside of Storybook (plain Vitest, Jest, Playwright CT).
|
|
18
|
+
*
|
|
19
|
+
* Usage in your test setup file:
|
|
20
|
+
*
|
|
21
|
+
* import { enableAccessLint } from "@accesslint/storybook-addon/portable";
|
|
22
|
+
* import { setProjectAnnotations } from "@storybook/react";
|
|
23
|
+
* import * as previewAnnotations from "./.storybook/preview";
|
|
24
|
+
*
|
|
25
|
+
* const project = setProjectAnnotations([
|
|
26
|
+
* previewAnnotations,
|
|
27
|
+
* enableAccessLint(),
|
|
28
|
+
* ]);
|
|
29
|
+
* beforeAll(project.beforeAll);
|
|
30
|
+
*
|
|
31
|
+
* Then in tests:
|
|
32
|
+
*
|
|
33
|
+
* import { composeStories } from "@storybook/react";
|
|
34
|
+
* import * as stories from "./Button.stories";
|
|
35
|
+
*
|
|
36
|
+
* const { Primary } = composeStories(stories);
|
|
37
|
+
*
|
|
38
|
+
* test("is accessible", async () => {
|
|
39
|
+
* await Primary.run();
|
|
40
|
+
* // AccessLint afterEach runs automatically and reports violations
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
declare function enableAccessLint(): typeof accesslintAnnotations;
|
|
45
|
+
|
|
46
|
+
export { accesslintAnnotations, enableAccessLint };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
declare const afterEach: ({ reporting, parameters, viewMode, tags, }: {
|
|
2
|
+
reporting: {
|
|
3
|
+
addReport: (report: Record<string, unknown>) => void;
|
|
4
|
+
};
|
|
5
|
+
parameters: Record<string, unknown>;
|
|
6
|
+
viewMode: string;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
|
|
10
|
+
declare const accesslintAnnotations_afterEach: typeof afterEach;
|
|
11
|
+
declare namespace accesslintAnnotations {
|
|
12
|
+
export { accesslintAnnotations_afterEach as afterEach };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Portable stories helper for using AccessLint with composeStories
|
|
17
|
+
* outside of Storybook (plain Vitest, Jest, Playwright CT).
|
|
18
|
+
*
|
|
19
|
+
* Usage in your test setup file:
|
|
20
|
+
*
|
|
21
|
+
* import { enableAccessLint } from "@accesslint/storybook-addon/portable";
|
|
22
|
+
* import { setProjectAnnotations } from "@storybook/react";
|
|
23
|
+
* import * as previewAnnotations from "./.storybook/preview";
|
|
24
|
+
*
|
|
25
|
+
* const project = setProjectAnnotations([
|
|
26
|
+
* previewAnnotations,
|
|
27
|
+
* enableAccessLint(),
|
|
28
|
+
* ]);
|
|
29
|
+
* beforeAll(project.beforeAll);
|
|
30
|
+
*
|
|
31
|
+
* Then in tests:
|
|
32
|
+
*
|
|
33
|
+
* import { composeStories } from "@storybook/react";
|
|
34
|
+
* import * as stories from "./Button.stories";
|
|
35
|
+
*
|
|
36
|
+
* const { Primary } = composeStories(stories);
|
|
37
|
+
*
|
|
38
|
+
* test("is accessible", async () => {
|
|
39
|
+
* await Primary.run();
|
|
40
|
+
* // AccessLint afterEach runs automatically and reports violations
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
declare function enableAccessLint(): typeof accesslintAnnotations;
|
|
45
|
+
|
|
46
|
+
export { accesslintAnnotations, enableAccessLint };
|
package/dist/portable.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { configureRules, runAudit, getRuleById } from '@accesslint/core';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __export = (target, all) => {
|
|
5
|
+
for (var name in all)
|
|
6
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// src/preview.ts
|
|
10
|
+
var preview_exports = {};
|
|
11
|
+
__export(preview_exports, {
|
|
12
|
+
afterEach: () => afterEach
|
|
13
|
+
});
|
|
14
|
+
configureRules({
|
|
15
|
+
disabledRules: ["accesslint-045"]
|
|
16
|
+
});
|
|
17
|
+
function scopeViolations(violations) {
|
|
18
|
+
const root = document.getElementById("storybook-root");
|
|
19
|
+
if (!root) return violations;
|
|
20
|
+
return violations.filter((v) => {
|
|
21
|
+
const local = v.selector.replace(/^.*>>>\s*iframe>\s*/, "");
|
|
22
|
+
try {
|
|
23
|
+
const el = document.querySelector(local);
|
|
24
|
+
return el && root.contains(el);
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function enrichViolations(violations) {
|
|
31
|
+
return violations.map((v) => {
|
|
32
|
+
const rule = getRuleById(v.ruleId);
|
|
33
|
+
return {
|
|
34
|
+
...v,
|
|
35
|
+
element: void 0,
|
|
36
|
+
// not serializable
|
|
37
|
+
description: rule?.description,
|
|
38
|
+
wcag: rule?.wcag,
|
|
39
|
+
level: rule?.level,
|
|
40
|
+
guidance: rule?.guidance
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
var afterEach = async ({
|
|
45
|
+
reporting,
|
|
46
|
+
parameters,
|
|
47
|
+
viewMode,
|
|
48
|
+
tags
|
|
49
|
+
}) => {
|
|
50
|
+
const accesslintParam = parameters?.accesslint;
|
|
51
|
+
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
52
|
+
if (viewMode !== "story") return;
|
|
53
|
+
if (tags?.includes("no-a11y")) return;
|
|
54
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
55
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
56
|
+
const result = runAudit(document);
|
|
57
|
+
const scoped = scopeViolations(result.violations);
|
|
58
|
+
const enriched = enrichViolations(scoped);
|
|
59
|
+
const hasViolations = enriched.length > 0;
|
|
60
|
+
const mode = accesslintParam?.test === "todo" ? "warning" : "failed";
|
|
61
|
+
reporting.addReport({
|
|
62
|
+
type: "accesslint",
|
|
63
|
+
version: 1,
|
|
64
|
+
result: {
|
|
65
|
+
violations: enriched,
|
|
66
|
+
ruleCount: result.ruleCount
|
|
67
|
+
},
|
|
68
|
+
status: hasViolations ? mode : "passed"
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/portable.ts
|
|
73
|
+
function enableAccessLint() {
|
|
74
|
+
return preview_exports;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { preview_exports as accesslintAnnotations, enableAccessLint };
|
package/dist/preview.cjs
CHANGED
|
@@ -36,11 +36,15 @@ function enrichViolations(violations) {
|
|
|
36
36
|
var afterEach = async ({
|
|
37
37
|
reporting,
|
|
38
38
|
parameters,
|
|
39
|
-
viewMode
|
|
39
|
+
viewMode,
|
|
40
|
+
tags
|
|
40
41
|
}) => {
|
|
41
42
|
const accesslintParam = parameters?.accesslint;
|
|
42
43
|
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
43
44
|
if (viewMode !== "story") return;
|
|
45
|
+
if (tags?.includes("no-a11y")) return;
|
|
46
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
47
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
44
48
|
const result = core.runAudit(document);
|
|
45
49
|
const scoped = scopeViolations(result.violations);
|
|
46
50
|
const enriched = enrichViolations(scoped);
|
package/dist/preview.d.cts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
declare const afterEach: ({ reporting, parameters, viewMode, }: {
|
|
1
|
+
declare const afterEach: ({ reporting, parameters, viewMode, tags, }: {
|
|
2
2
|
reporting: {
|
|
3
3
|
addReport: (report: Record<string, unknown>) => void;
|
|
4
4
|
};
|
|
5
5
|
parameters: Record<string, unknown>;
|
|
6
6
|
viewMode: string;
|
|
7
|
+
tags?: string[];
|
|
7
8
|
}) => Promise<void>;
|
|
8
9
|
|
|
9
10
|
export { afterEach };
|
package/dist/preview.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
declare const afterEach: ({ reporting, parameters, viewMode, }: {
|
|
1
|
+
declare const afterEach: ({ reporting, parameters, viewMode, tags, }: {
|
|
2
2
|
reporting: {
|
|
3
3
|
addReport: (report: Record<string, unknown>) => void;
|
|
4
4
|
};
|
|
5
5
|
parameters: Record<string, unknown>;
|
|
6
6
|
viewMode: string;
|
|
7
|
+
tags?: string[];
|
|
7
8
|
}) => Promise<void>;
|
|
8
9
|
|
|
9
10
|
export { afterEach };
|
package/dist/preview.js
CHANGED
|
@@ -34,11 +34,15 @@ function enrichViolations(violations) {
|
|
|
34
34
|
var afterEach = async ({
|
|
35
35
|
reporting,
|
|
36
36
|
parameters,
|
|
37
|
-
viewMode
|
|
37
|
+
viewMode,
|
|
38
|
+
tags
|
|
38
39
|
}) => {
|
|
39
40
|
const accesslintParam = parameters?.accesslint;
|
|
40
41
|
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
41
42
|
if (viewMode !== "story") return;
|
|
43
|
+
if (tags?.includes("no-a11y")) return;
|
|
44
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
45
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
42
46
|
const result = runAudit(document);
|
|
43
47
|
const scoped = scopeViolations(result.violations);
|
|
44
48
|
const enriched = enrichViolations(scoped);
|
package/dist/vitest-plugin.cjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
4
4
|
// src/vitest-plugin.ts
|
|
5
|
-
function accesslintTest() {
|
|
5
|
+
function accesslintTest(options) {
|
|
6
6
|
const distDir = new URL(".", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('vitest-plugin.cjs', document.baseURI).href))).pathname;
|
|
7
7
|
return {
|
|
8
8
|
name: "@accesslint/storybook-addon",
|
|
9
9
|
config() {
|
|
10
|
-
|
|
10
|
+
const config = {
|
|
11
11
|
server: {
|
|
12
12
|
fs: {
|
|
13
13
|
allow: [distDir]
|
|
@@ -17,6 +17,12 @@ function accesslintTest() {
|
|
|
17
17
|
setupFiles: ["@accesslint/storybook-addon/vitest-setup"]
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
+
if (options?.tags) {
|
|
21
|
+
config.define = {
|
|
22
|
+
"__ACCESSLINT_SKIP_TAGS__": JSON.stringify(options.tags.skip ?? [])
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return config;
|
|
20
26
|
}
|
|
21
27
|
};
|
|
22
28
|
}
|
package/dist/vitest-plugin.d.cts
CHANGED
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
* plugins: [storybookTest(), accesslintTest()],
|
|
12
12
|
* });
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
interface AccessLintTestOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Tags-based filtering for which stories to audit.
|
|
17
|
+
*
|
|
18
|
+
* accesslintTest({ tags: { skip: ["no-a11y"] } })
|
|
19
|
+
*
|
|
20
|
+
* Stories with any of the `skip` tags will not be audited.
|
|
21
|
+
*/
|
|
22
|
+
tags?: {
|
|
23
|
+
skip?: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
declare function accesslintTest(options?: AccessLintTestOptions): {
|
|
15
27
|
name: string;
|
|
16
28
|
config: () => Record<string, unknown>;
|
|
17
29
|
};
|
|
18
30
|
|
|
19
|
-
export { accesslintTest };
|
|
31
|
+
export { type AccessLintTestOptions, accesslintTest };
|
package/dist/vitest-plugin.d.ts
CHANGED
|
@@ -11,9 +11,21 @@
|
|
|
11
11
|
* plugins: [storybookTest(), accesslintTest()],
|
|
12
12
|
* });
|
|
13
13
|
*/
|
|
14
|
-
|
|
14
|
+
interface AccessLintTestOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Tags-based filtering for which stories to audit.
|
|
17
|
+
*
|
|
18
|
+
* accesslintTest({ tags: { skip: ["no-a11y"] } })
|
|
19
|
+
*
|
|
20
|
+
* Stories with any of the `skip` tags will not be audited.
|
|
21
|
+
*/
|
|
22
|
+
tags?: {
|
|
23
|
+
skip?: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
declare function accesslintTest(options?: AccessLintTestOptions): {
|
|
15
27
|
name: string;
|
|
16
28
|
config: () => Record<string, unknown>;
|
|
17
29
|
};
|
|
18
30
|
|
|
19
|
-
export { accesslintTest };
|
|
31
|
+
export { type AccessLintTestOptions, accesslintTest };
|
package/dist/vitest-plugin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/vitest-plugin.ts
|
|
2
|
-
function accesslintTest() {
|
|
2
|
+
function accesslintTest(options) {
|
|
3
3
|
const distDir = new URL(".", import.meta.url).pathname;
|
|
4
4
|
return {
|
|
5
5
|
name: "@accesslint/storybook-addon",
|
|
6
6
|
config() {
|
|
7
|
-
|
|
7
|
+
const config = {
|
|
8
8
|
server: {
|
|
9
9
|
fs: {
|
|
10
10
|
allow: [distDir]
|
|
@@ -14,6 +14,12 @@ function accesslintTest() {
|
|
|
14
14
|
setupFiles: ["@accesslint/storybook-addon/vitest-setup"]
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
+
if (options?.tags) {
|
|
18
|
+
config.define = {
|
|
19
|
+
"__ACCESSLINT_SKIP_TAGS__": JSON.stringify(options.tags.skip ?? [])
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return config;
|
|
17
23
|
}
|
|
18
24
|
};
|
|
19
25
|
}
|
package/dist/vitest-setup.cjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var test = require('storybook/test');
|
|
4
|
+
var vitest = require('vitest');
|
|
3
5
|
var previewApi = require('storybook/preview-api');
|
|
4
6
|
var core = require('@accesslint/core');
|
|
7
|
+
var matchers = require('@accesslint/vitest/matchers');
|
|
5
8
|
|
|
6
9
|
var __defProp = Object.defineProperty;
|
|
7
10
|
var __export = (target, all) => {
|
|
@@ -47,11 +50,15 @@ function enrichViolations(violations) {
|
|
|
47
50
|
var afterEach = async ({
|
|
48
51
|
reporting,
|
|
49
52
|
parameters,
|
|
50
|
-
viewMode
|
|
53
|
+
viewMode,
|
|
54
|
+
tags
|
|
51
55
|
}) => {
|
|
52
56
|
const accesslintParam = parameters?.accesslint;
|
|
53
57
|
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
54
58
|
if (viewMode !== "story") return;
|
|
59
|
+
if (tags?.includes("no-a11y")) return;
|
|
60
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
61
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
55
62
|
const result = core.runAudit(document);
|
|
56
63
|
const scoped = scopeViolations(result.violations);
|
|
57
64
|
const enriched = enrichViolations(scoped);
|
|
@@ -67,8 +74,8 @@ var afterEach = async ({
|
|
|
67
74
|
status: hasViolations ? mode : "passed"
|
|
68
75
|
});
|
|
69
76
|
};
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
test.expect.extend(matchers.accesslintMatchers);
|
|
78
|
+
vitest.expect.extend(matchers.accesslintMatchers);
|
|
72
79
|
var g = globalThis;
|
|
73
80
|
var existing = g.globalProjectAnnotations;
|
|
74
81
|
g.globalProjectAnnotations = existing ? previewApi.composeConfigs([existing, preview_exports]) : previewApi.composeConfigs([preview_exports]);
|
package/dist/vitest-setup.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { expect } from 'storybook/test';
|
|
2
|
+
import { expect as expect$1 } from 'vitest';
|
|
1
3
|
import { composeConfigs } from 'storybook/preview-api';
|
|
2
4
|
import { configureRules, runAudit, getRuleById } from '@accesslint/core';
|
|
5
|
+
import { accesslintMatchers } from '@accesslint/vitest/matchers';
|
|
3
6
|
|
|
4
7
|
var __defProp = Object.defineProperty;
|
|
5
8
|
var __export = (target, all) => {
|
|
@@ -45,11 +48,15 @@ function enrichViolations(violations) {
|
|
|
45
48
|
var afterEach = async ({
|
|
46
49
|
reporting,
|
|
47
50
|
parameters,
|
|
48
|
-
viewMode
|
|
51
|
+
viewMode,
|
|
52
|
+
tags
|
|
49
53
|
}) => {
|
|
50
54
|
const accesslintParam = parameters?.accesslint;
|
|
51
55
|
if (accesslintParam?.disable === true || accesslintParam?.test === "off") return;
|
|
52
56
|
if (viewMode !== "story") return;
|
|
57
|
+
if (tags?.includes("no-a11y")) return;
|
|
58
|
+
const skipTags = typeof __ACCESSLINT_SKIP_TAGS__ !== "undefined" ? __ACCESSLINT_SKIP_TAGS__ : [];
|
|
59
|
+
if (skipTags.length > 0 && tags?.some((t) => skipTags.includes(t))) return;
|
|
53
60
|
const result = runAudit(document);
|
|
54
61
|
const scoped = scopeViolations(result.violations);
|
|
55
62
|
const enriched = enrichViolations(scoped);
|
|
@@ -65,8 +72,8 @@ var afterEach = async ({
|
|
|
65
72
|
status: hasViolations ? mode : "passed"
|
|
66
73
|
});
|
|
67
74
|
};
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
expect.extend(accesslintMatchers);
|
|
76
|
+
expect$1.extend(accesslintMatchers);
|
|
70
77
|
var g = globalThis;
|
|
71
78
|
var existing = g.globalProjectAnnotations;
|
|
72
79
|
g.globalProjectAnnotations = existing ? composeConfigs([existing, preview_exports]) : composeConfigs([preview_exports]);
|
package/matchers.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AccessibleMatcherOptions } from "@accesslint/vitest/matchers";
|
|
2
|
+
|
|
3
|
+
declare module "vitest" {
|
|
4
|
+
interface Assertion<T> {
|
|
5
|
+
toBeAccessible(options?: AccessibleMatcherOptions): void;
|
|
6
|
+
}
|
|
7
|
+
interface AsymmetricMatchersContaining {
|
|
8
|
+
toBeAccessible(options?: AccessibleMatcherOptions): void;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@accesslint/storybook-addon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Catch accessibility violations in your Storybook stories as you develop",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -30,8 +30,17 @@
|
|
|
30
30
|
"require": "./dist/vitest-plugin.cjs"
|
|
31
31
|
},
|
|
32
32
|
"./vitest-setup": {
|
|
33
|
+
"types": "./dist/vitest-setup.d.ts",
|
|
33
34
|
"import": "./dist/vitest-setup.js",
|
|
34
35
|
"require": "./dist/vitest-setup.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./matchers": {
|
|
38
|
+
"import": "./dist/matchers.js",
|
|
39
|
+
"require": "./dist/matchers.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./portable": {
|
|
42
|
+
"import": "./dist/portable.js",
|
|
43
|
+
"require": "./dist/portable.cjs"
|
|
35
44
|
}
|
|
36
45
|
},
|
|
37
46
|
"main": "dist/index.cjs",
|
|
@@ -49,17 +58,19 @@
|
|
|
49
58
|
"typecheck": "tsc --noEmit"
|
|
50
59
|
},
|
|
51
60
|
"dependencies": {
|
|
52
|
-
"@accesslint/core": "^0.6.5"
|
|
61
|
+
"@accesslint/core": "^0.6.5",
|
|
62
|
+
"@accesslint/vitest": "^0.1.3"
|
|
53
63
|
},
|
|
54
64
|
"devDependencies": {
|
|
55
65
|
"react": "^18.2.0",
|
|
56
66
|
"react-dom": "^18.2.0",
|
|
57
67
|
"storybook": "^10.2.0",
|
|
58
68
|
"tsup": "^8.4.0",
|
|
59
|
-
"typescript": "^5.7.0"
|
|
69
|
+
"typescript": "^5.7.0",
|
|
70
|
+
"vitest": "^3.0.0"
|
|
60
71
|
},
|
|
61
72
|
"peerDependencies": {
|
|
62
|
-
"storybook": "^10.0.0"
|
|
73
|
+
"storybook": "^9.0.0 || ^10.0.0"
|
|
63
74
|
},
|
|
64
75
|
"keywords": [
|
|
65
76
|
"storybook-addon",
|