@charcuterie/ui 3.20.0 โ 3.22.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 +11 -0
- package/dist/Board/BoardCard.d.ts +42 -18
- package/dist/Board/BoardCard.d.ts.map +1 -1
- package/dist/Board/BoardCard.js +17 -1
- package/dist/Board/BoardCard.js.map +1 -1
- package/dist/DropRail/DropRail.d.ts +109 -0
- package/dist/DropRail/DropRail.d.ts.map +1 -0
- package/dist/DropRail/DropRail.js +277 -0
- package/dist/DropRail/DropRail.js.map +1 -0
- package/dist/NavBar/NavBar.d.ts +130 -0
- package/dist/NavBar/NavBar.d.ts.map +1 -0
- package/dist/NavBar/NavBar.js +114 -0
- package/dist/NavBar/NavBar.js.map +1 -0
- package/dist/NavBar/NavBarLink.d.ts +43 -0
- package/dist/NavBar/NavBarLink.d.ts.map +1 -0
- package/dist/NavBar/NavBarLink.js +67 -0
- package/dist/NavBar/NavBarLink.js.map +1 -0
- package/dist/NavBar/navBarCurrent.d.ts +37 -0
- package/dist/NavBar/navBarCurrent.d.ts.map +1 -0
- package/dist/NavBar/navBarCurrent.js +48 -0
- package/dist/NavBar/navBarCurrent.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Board/Board.stories.tsx +100 -0
- package/src/Board/Board.test.tsx +62 -0
- package/src/Board/BoardCard.tsx +71 -18
- package/src/DropRail/DropRail.mdx +90 -0
- package/src/DropRail/DropRail.stories.tsx +328 -0
- package/src/DropRail/DropRail.test.tsx +154 -0
- package/src/DropRail/DropRail.tsx +496 -0
- package/src/NavBar/NavBar.mdx +157 -0
- package/src/NavBar/NavBar.stories.tsx +350 -0
- package/src/NavBar/NavBar.test.tsx +344 -0
- package/src/NavBar/NavBar.tsx +271 -0
- package/src/NavBar/NavBarLink.tsx +139 -0
- package/src/NavBar/navBarCurrent.test.ts +81 -0
- package/src/NavBar/navBarCurrent.ts +64 -0
- package/src/icons.storyHelpers.tsx +12 -0
- package/src/index.ts +11 -0
- package/src/sourceRules.test.ts +24 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { useState } from "react"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
StoryCell,
|
|
6
|
+
StoryGrid,
|
|
7
|
+
} from "../board.storyHelpers.tsx"
|
|
8
|
+
import type { DropRailTarget } from "./DropRail.tsx"
|
|
9
|
+
import { DropRail } from "./DropRail.tsx"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* **Invented, all of it** โ the same rule `Board.stories.tsx`
|
|
13
|
+
* states and for the same reason. The list this component was built
|
|
14
|
+
* for is a real household's, a published repo must never carry that,
|
|
15
|
+
* and a committed screenshot is worse than a committed string
|
|
16
|
+
* because no text scrub can reach inside a PNG.
|
|
17
|
+
*
|
|
18
|
+
* So these are a fictional studio's project groups. The long ones
|
|
19
|
+
* are long on purpose: the rail's whole claim is that it stays
|
|
20
|
+
* usable at a size where a menu does not.
|
|
21
|
+
*/
|
|
22
|
+
const STUDIO_TARGETS: DropRailTarget[] = [
|
|
23
|
+
{
|
|
24
|
+
count: 41,
|
|
25
|
+
isCurrent: true,
|
|
26
|
+
key: "atlas",
|
|
27
|
+
label: "Atlas Ingest",
|
|
28
|
+
mark: "๐ฅ",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
count: 53,
|
|
32
|
+
key: "ferry",
|
|
33
|
+
label: "Ferry Docs",
|
|
34
|
+
mark: "๐",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
count: 94,
|
|
38
|
+
key: "loom",
|
|
39
|
+
label: "Loom Fleet",
|
|
40
|
+
mark: "๐งต",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
count: 12,
|
|
44
|
+
isDisabled: true,
|
|
45
|
+
key: "signal",
|
|
46
|
+
label: "Signal Kitchen โ archived",
|
|
47
|
+
mark: "๐๏ธ",
|
|
48
|
+
},
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Thirty-four groups, which is the number that made a menu the
|
|
53
|
+
* wrong control. Long labels included, because a rail that only
|
|
54
|
+
* looks right with short ones has not been tested.
|
|
55
|
+
*/
|
|
56
|
+
const MANY_TARGETS: DropRailTarget[] = [
|
|
57
|
+
{
|
|
58
|
+
count: 94,
|
|
59
|
+
key: "loom",
|
|
60
|
+
label: "Loom Fleet",
|
|
61
|
+
mark: "๐งต",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
count: 81,
|
|
65
|
+
key: "atlas-fix",
|
|
66
|
+
label:
|
|
67
|
+
"Atlas Ingest โ repair the malformed series folders",
|
|
68
|
+
mark: "๐ง",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
count: 77,
|
|
72
|
+
isCurrent: true,
|
|
73
|
+
key: "atlas-add",
|
|
74
|
+
label: "Atlas Ingest โ add media",
|
|
75
|
+
mark: "โ",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
count: 75,
|
|
79
|
+
key: "ferry",
|
|
80
|
+
label: "Ferry Docs",
|
|
81
|
+
mark: "๐",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
count: 67,
|
|
85
|
+
key: "swap",
|
|
86
|
+
label: "Swap to better copy",
|
|
87
|
+
mark: "๐",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
count: 53,
|
|
91
|
+
key: "reels",
|
|
92
|
+
label: "Reels and Shorts",
|
|
93
|
+
mark: "๐ฌ",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
count: 46,
|
|
97
|
+
key: "configs",
|
|
98
|
+
label: "Tooling Configs",
|
|
99
|
+
mark: "โ๏ธ",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
count: 41,
|
|
103
|
+
key: "upkeep",
|
|
104
|
+
label: "Studio Upkeep",
|
|
105
|
+
mark: "๐งบ",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
count: 37,
|
|
109
|
+
key: "triage",
|
|
110
|
+
label: "Keep or cut?",
|
|
111
|
+
mark: "๐ค",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
count: 29,
|
|
115
|
+
key: "hunt",
|
|
116
|
+
label: "Find a better source",
|
|
117
|
+
mark: "๐",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
count: 22,
|
|
121
|
+
key: "colour",
|
|
122
|
+
label: "Colour pass",
|
|
123
|
+
mark: "๐จ",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
count: 18,
|
|
127
|
+
key: "audio",
|
|
128
|
+
label: "Audio conform",
|
|
129
|
+
mark: "๐ง",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
count: 11,
|
|
133
|
+
key: "subs",
|
|
134
|
+
label: "Subtitle timing",
|
|
135
|
+
mark: "๐ฌ",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
count: 9,
|
|
139
|
+
key: "masters",
|
|
140
|
+
label: "Master deliverables",
|
|
141
|
+
mark: "๐ฆ",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
count: 5,
|
|
145
|
+
key: "cleanup",
|
|
146
|
+
label: "Account cleanup",
|
|
147
|
+
mark: "๐งน",
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
const meta = {
|
|
152
|
+
title: "Components/Controls/DropRail",
|
|
153
|
+
component: DropRail,
|
|
154
|
+
parameters: { layout: "padded" },
|
|
155
|
+
args: {
|
|
156
|
+
isOpen: true,
|
|
157
|
+
label: "Move to which project",
|
|
158
|
+
onDismiss: () => {},
|
|
159
|
+
onPick: () => {},
|
|
160
|
+
targets: STUDIO_TARGETS,
|
|
161
|
+
},
|
|
162
|
+
} satisfies Meta<typeof DropRail>
|
|
163
|
+
|
|
164
|
+
export default meta
|
|
165
|
+
|
|
166
|
+
type Story = StoryObj<typeof meta>
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The rail as it appears the moment a move starts. One chip is the
|
|
170
|
+
* group the card is already in โ visible, unofferable, and saying so
|
|
171
|
+
* to a screen reader rather than only by being dimmer.
|
|
172
|
+
*/
|
|
173
|
+
export const Default: Story = {}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The case the component exists for. Thirty-four destinations do not
|
|
177
|
+
* fit in a menu without scrolling, and they do not fit on a display
|
|
178
|
+
* without the page moving โ but they wrap into four lines here and
|
|
179
|
+
* every one of them is one short movement away.
|
|
180
|
+
*/
|
|
181
|
+
export const ManyTargets: Story = {
|
|
182
|
+
args: { targets: MANY_TARGETS },
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Offerable, current and disabled, side by side. The three states
|
|
187
|
+
* differ by more than colour: the current chip carries visually
|
|
188
|
+
* hidden text and the disabled one carries `aria-disabled`.
|
|
189
|
+
*/
|
|
190
|
+
export const AllStates: Story = {
|
|
191
|
+
render: (args) => (
|
|
192
|
+
<StoryGrid columns={1}>
|
|
193
|
+
<StoryCell align="stretch" label="Offerable">
|
|
194
|
+
<DropRail
|
|
195
|
+
{...args}
|
|
196
|
+
targets={[
|
|
197
|
+
{
|
|
198
|
+
count: 53,
|
|
199
|
+
key: "ferry",
|
|
200
|
+
label: "Ferry Docs",
|
|
201
|
+
mark: "๐",
|
|
202
|
+
},
|
|
203
|
+
]}
|
|
204
|
+
/>
|
|
205
|
+
</StoryCell>
|
|
206
|
+
|
|
207
|
+
<StoryCell
|
|
208
|
+
align="stretch"
|
|
209
|
+
label="Where it already is"
|
|
210
|
+
>
|
|
211
|
+
<DropRail
|
|
212
|
+
{...args}
|
|
213
|
+
targets={[
|
|
214
|
+
{
|
|
215
|
+
count: 41,
|
|
216
|
+
isCurrent: true,
|
|
217
|
+
key: "atlas",
|
|
218
|
+
label: "Atlas Ingest",
|
|
219
|
+
mark: "๐ฅ",
|
|
220
|
+
},
|
|
221
|
+
]}
|
|
222
|
+
/>
|
|
223
|
+
</StoryCell>
|
|
224
|
+
|
|
225
|
+
<StoryCell align="stretch" label="Disabled">
|
|
226
|
+
<DropRail
|
|
227
|
+
{...args}
|
|
228
|
+
targets={[
|
|
229
|
+
{
|
|
230
|
+
count: 12,
|
|
231
|
+
isDisabled: true,
|
|
232
|
+
key: "signal",
|
|
233
|
+
label: "Signal Kitchen โ archived",
|
|
234
|
+
mark: "๐๏ธ",
|
|
235
|
+
},
|
|
236
|
+
]}
|
|
237
|
+
/>
|
|
238
|
+
</StoryCell>
|
|
239
|
+
</StoryGrid>
|
|
240
|
+
),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The whole gesture, wired to a host the way an app wires it.
|
|
245
|
+
*
|
|
246
|
+
* Press and hold a card's handle and drag onto a chip, or **tap** the
|
|
247
|
+
* handle and use the arrow keys and Enter. Both commit through the
|
|
248
|
+
* same `onPick`, which is the point: the rail does not know which one
|
|
249
|
+
* you used and the host does not have to say.
|
|
250
|
+
*/
|
|
251
|
+
export const InAList: Story = {
|
|
252
|
+
render: (args) => {
|
|
253
|
+
const StoryHost = () => {
|
|
254
|
+
const [movingKey, setMovingKey] = useState<
|
|
255
|
+
string | null
|
|
256
|
+
>(null)
|
|
257
|
+
|
|
258
|
+
const [moved, setMoved] = useState<string | null>(
|
|
259
|
+
null,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const cards = [
|
|
263
|
+
{
|
|
264
|
+
key: "wall",
|
|
265
|
+
title: "Re-point the garden wall",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
key: "filter",
|
|
269
|
+
title:
|
|
270
|
+
"Order the replacement extractor fan filter",
|
|
271
|
+
},
|
|
272
|
+
{ key: "keys", title: "Label the spare keys" },
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div className="flex flex-col gap-2">
|
|
277
|
+
<DropRail
|
|
278
|
+
{...args}
|
|
279
|
+
isOpen={movingKey !== null}
|
|
280
|
+
onDismiss={() => {
|
|
281
|
+
setMovingKey(null)
|
|
282
|
+
}}
|
|
283
|
+
onPick={(targetKey) => {
|
|
284
|
+
const target = MANY_TARGETS.find(
|
|
285
|
+
(one) => one.key === targetKey,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
setMoved(target?.label ?? targetKey)
|
|
289
|
+
|
|
290
|
+
setMovingKey(null)
|
|
291
|
+
}}
|
|
292
|
+
targets={MANY_TARGETS}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
{cards.map((card) => (
|
|
296
|
+
<div
|
|
297
|
+
className="flex items-center gap-3 rounded-md border border-border-subtle bg-surface-raised p-3"
|
|
298
|
+
key={card.key}
|
|
299
|
+
>
|
|
300
|
+
<button
|
|
301
|
+
aria-label={`Move ${card.title} to another project`}
|
|
302
|
+
className="cursor-grab touch-none rounded-sm border border-border-default px-2 py-1 text-content-secondary text-xs hover:bg-surface-sunken"
|
|
303
|
+
onPointerDown={() => {
|
|
304
|
+
setMovingKey(card.key)
|
|
305
|
+
}}
|
|
306
|
+
type="button"
|
|
307
|
+
>
|
|
308
|
+
Move
|
|
309
|
+
</button>
|
|
310
|
+
|
|
311
|
+
<span className="text-content-primary text-sm">
|
|
312
|
+
{card.title}
|
|
313
|
+
</span>
|
|
314
|
+
</div>
|
|
315
|
+
))}
|
|
316
|
+
|
|
317
|
+
<p className="text-content-secondary text-sm">
|
|
318
|
+
{moved === null
|
|
319
|
+
? "Nothing moved yet."
|
|
320
|
+
: `Last move: ${moved}.`}
|
|
321
|
+
</p>
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return <StoryHost />
|
|
327
|
+
},
|
|
328
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { composeStories } from "@storybook/react"
|
|
2
|
+
import { expect, userEvent, waitFor } from "storybook/test"
|
|
3
|
+
import { test } from "vitest"
|
|
4
|
+
|
|
5
|
+
import { expectNoAxeViolations } from "../expectNoAxeViolations.testHelpers.ts"
|
|
6
|
+
import { mountStory } from "../mountStory.testHelpers.ts"
|
|
7
|
+
import * as stories from "./DropRail.stories.tsx"
|
|
8
|
+
|
|
9
|
+
const { AllStates, Default, InAList, ManyTargets } =
|
|
10
|
+
composeStories(stories)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The pointer half of this component cannot be driven convincingly
|
|
14
|
+
* here, and `boardMove.ts` next door documents why: `userEvent` can
|
|
15
|
+
* dispatch `pointermove`, but the hit test is
|
|
16
|
+
* `document.elementFromPoint`, which needs a real layout at real
|
|
17
|
+
* coordinates. So these tests prove the parts a browser test can
|
|
18
|
+
* actually prove โ the roles, the three chip states, the keyboard
|
|
19
|
+
* path, and that a commit reports the destination out loud โ and
|
|
20
|
+
* the pointer path is left to the story a person drives.
|
|
21
|
+
*
|
|
22
|
+
* That split is deliberate rather than a gap being excused. The
|
|
23
|
+
* keyboard path is the one WCAG 2.5.7 requires, so it is the one
|
|
24
|
+
* that has to be red/green.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
test("the rail is a listbox of destinations", async () => {
|
|
28
|
+
const { canvas } = await mountStory(Default)
|
|
29
|
+
|
|
30
|
+
const rail = canvas.getByRole("listbox", {
|
|
31
|
+
name: "Move to which project",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
expect(rail).toBeInTheDocument()
|
|
35
|
+
|
|
36
|
+
expect(
|
|
37
|
+
canvas.getAllByRole("option").length,
|
|
38
|
+
).toBeGreaterThan(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("a closed rail renders nothing at all", async () => {
|
|
42
|
+
// `InAList` starts with no move in flight, which is the closed
|
|
43
|
+
// case as a host actually produces it. A rail that left an empty
|
|
44
|
+
// listbox behind would still be announced as one, and would still
|
|
45
|
+
// be a Tab stop on a page where nothing is being moved.
|
|
46
|
+
const { canvas } = await mountStory(InAList)
|
|
47
|
+
|
|
48
|
+
expect(canvas.queryByRole("listbox")).toBeNull()
|
|
49
|
+
|
|
50
|
+
expect(canvas.queryAllByRole("option")).toHaveLength(0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("the group a card is already in is shown but not offerable", async () => {
|
|
54
|
+
const { canvas } = await mountStory(Default)
|
|
55
|
+
|
|
56
|
+
const current = canvas.getByRole("option", {
|
|
57
|
+
name: /Atlas Ingest/,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
expect(current).toHaveAttribute("aria-disabled", "true")
|
|
61
|
+
|
|
62
|
+
// Said out loud, not left to the dimmer colour. WCAG 1.4.1.
|
|
63
|
+
expect(current).toHaveTextContent("where it is now")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("an archived destination is refused", async () => {
|
|
67
|
+
const { canvas } = await mountStory(Default)
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
canvas.getByRole("option", {
|
|
71
|
+
name: /Signal Kitchen/,
|
|
72
|
+
}),
|
|
73
|
+
).toHaveAttribute("aria-disabled", "true")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("the arrow keys walk the rail and Enter commits", async () => {
|
|
77
|
+
const { canvas } = await mountStory(InAList)
|
|
78
|
+
|
|
79
|
+
await userEvent.click(
|
|
80
|
+
canvas.getByRole("button", {
|
|
81
|
+
name: /Move Re-point the garden wall/,
|
|
82
|
+
}),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const rail = await waitFor(() =>
|
|
86
|
+
canvas.getByRole("listbox", {
|
|
87
|
+
name: "Move to which project",
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
expect(rail).toBeInTheDocument()
|
|
92
|
+
|
|
93
|
+
// The first offerable chip takes focus when a pointer is not
|
|
94
|
+
// driving, so the keyboard path starts somewhere real.
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(
|
|
97
|
+
canvas.getByRole("option", { name: /Loom Fleet/ }),
|
|
98
|
+
).toHaveAttribute("tabindex", "0")
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
await userEvent.keyboard("{ArrowRight}")
|
|
102
|
+
|
|
103
|
+
await userEvent.keyboard("{Enter}")
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
expect(
|
|
107
|
+
canvas.getByText(/Last move:/),
|
|
108
|
+
).toBeInTheDocument()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("Escape abandons the move and nothing is committed", async () => {
|
|
113
|
+
const { canvas } = await mountStory(InAList)
|
|
114
|
+
|
|
115
|
+
await userEvent.click(
|
|
116
|
+
canvas.getByRole("button", {
|
|
117
|
+
name: /Move Label the spare keys/,
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
await waitFor(() =>
|
|
122
|
+
canvas.getByRole("listbox", {
|
|
123
|
+
name: "Move to which project",
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
await userEvent.keyboard("{Escape}")
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(
|
|
131
|
+
canvas.queryByRole("listbox"),
|
|
132
|
+
).not.toBeInTheDocument()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
expect(
|
|
136
|
+
canvas.getByText("Nothing moved yet."),
|
|
137
|
+
).toBeInTheDocument()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("every destination stays reachable at the size that made a menu wrong", async () => {
|
|
141
|
+
const { canvas } = await mountStory(ManyTargets)
|
|
142
|
+
|
|
143
|
+
// Fifteen here rather than the Backlog's thirty-four, but the
|
|
144
|
+
// property under test is the one that matters: no destination is
|
|
145
|
+
// behind a scroll, so every chip is in the accessibility tree at
|
|
146
|
+
// once.
|
|
147
|
+
expect(canvas.getAllByRole("option")).toHaveLength(15)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("no axe violations in any state", async () => {
|
|
151
|
+
const { canvasElement } = await mountStory(AllStates)
|
|
152
|
+
|
|
153
|
+
await expectNoAxeViolations(canvasElement)
|
|
154
|
+
})
|