@charcuterie/ui 3.32.0 → 3.34.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 +30 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts +16 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.d.ts.map +1 -1
- package/dist/AdaptiveGrid/useAdaptiveColumns.js +1 -0
- package/dist/AdaptiveGrid/useAdaptiveColumns.js.map +1 -1
- package/dist/Board/Board.d.ts.map +1 -1
- package/dist/Board/Board.js +2 -2
- package/dist/Board/Board.js.map +1 -1
- package/dist/RangeSlider/RangeSlider.d.ts +117 -0
- package/dist/RangeSlider/RangeSlider.d.ts.map +1 -0
- package/dist/RangeSlider/RangeSlider.js +242 -0
- package/dist/RangeSlider/RangeSlider.js.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts +102 -0
- package/dist/RangeSlider/rangeSliderValue.d.ts.map +1 -0
- package/dist/RangeSlider/rangeSliderValue.js +116 -0
- package/dist/RangeSlider/rangeSliderValue.js.map +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +12 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +3 -3
- package/dist/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts +2 -1
- package/dist/SegmentedControl/SegmentedOption.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedOption.js +7 -2
- package/dist/SegmentedControl/SegmentedOption.js.map +1 -1
- package/dist/Slider/Slider.d.ts +6 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +8 -22
- package/dist/Slider/Slider.js.map +1 -1
- package/dist/TimecodeInput/TimecodeInput.d.ts +88 -0
- package/dist/TimecodeInput/TimecodeInput.d.ts.map +1 -0
- package/dist/TimecodeInput/TimecodeInput.js +258 -0
- package/dist/TimecodeInput/TimecodeInput.js.map +1 -0
- package/dist/TimecodeInput/timecode.d.ts +132 -0
- package/dist/TimecodeInput/timecode.d.ts.map +1 -0
- package/dist/TimecodeInput/timecode.js +246 -0
- package/dist/TimecodeInput/timecode.js.map +1 -0
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts +35 -0
- package/dist/VirtualizedGrid/VirtualizedGrid.d.ts.map +1 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js +37 -1
- package/dist/VirtualizedGrid/VirtualizedGrid.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/sliderStyles.d.ts +39 -0
- package/dist/sliderStyles.d.ts.map +1 -0
- package/dist/sliderStyles.js +50 -0
- package/dist/sliderStyles.js.map +1 -0
- package/package.json +1 -1
- package/src/AdaptiveGrid/useAdaptiveColumns.ts +17 -0
- package/src/Board/Board.test.tsx +8 -0
- package/src/Board/Board.tsx +7 -1
- package/src/RangeSlider/RangeSlider.mdx +85 -0
- package/src/RangeSlider/RangeSlider.stories.tsx +346 -0
- package/src/RangeSlider/RangeSlider.test.tsx +452 -0
- package/src/RangeSlider/RangeSlider.tsx +595 -0
- package/src/RangeSlider/rangeSliderValue.test.ts +217 -0
- package/src/RangeSlider/rangeSliderValue.ts +185 -0
- package/src/SegmentedControl/SegmentedControl.stories.tsx +50 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +36 -1
- package/src/SegmentedControl/SegmentedControl.tsx +14 -0
- package/src/SegmentedControl/SegmentedOption.tsx +7 -0
- package/src/Slider/Slider.tsx +23 -29
- package/src/TimecodeInput/TimecodeInput.mdx +155 -0
- package/src/TimecodeInput/TimecodeInput.stories.tsx +304 -0
- package/src/TimecodeInput/TimecodeInput.test.tsx +279 -0
- package/src/TimecodeInput/TimecodeInput.tsx +493 -0
- package/src/TimecodeInput/timecode.test.ts +236 -0
- package/src/TimecodeInput/timecode.ts +391 -0
- package/src/VirtualizedGrid/VirtualizedGrid.mdx +21 -0
- package/src/VirtualizedGrid/VirtualizedGrid.stories.tsx +64 -0
- package/src/VirtualizedGrid/VirtualizedGrid.test.tsx +122 -3
- package/src/VirtualizedGrid/VirtualizedGrid.tsx +37 -1
- package/src/index.ts +18 -0
- package/src/sliderStyles.ts +69 -0
- package/src/sourceRules.test.ts +25 -1
|
@@ -0,0 +1,452 @@
|
|
|
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 { expectAgentDrivable } from "../testing/index.ts"
|
|
8
|
+
import * as stories from "./RangeSlider.stories.tsx"
|
|
9
|
+
|
|
10
|
+
const { AllStates, BesideTheSlider, Default, Interactive } =
|
|
11
|
+
composeStories(stories)
|
|
12
|
+
|
|
13
|
+
/** A fraction along the bar, as viewport coordinates. */
|
|
14
|
+
const pointerAt =
|
|
15
|
+
(group: HTMLElement) =>
|
|
16
|
+
(fraction: number): { x: number; y: number } => {
|
|
17
|
+
const bar = group.getBoundingClientRect()
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
x: bar.x + bar.width * fraction,
|
|
21
|
+
y: bar.y + bar.height / 2,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Everything about a thumb that `../sliderStyles.ts` claims to own. */
|
|
26
|
+
const readThumbStyles = (thumb: Element) => {
|
|
27
|
+
const style = getComputedStyle(thumb)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
backgroundColor: style.backgroundColor,
|
|
31
|
+
borderColor: style.borderTopColor,
|
|
32
|
+
borderRadius: style.borderTopLeftRadius,
|
|
33
|
+
borderWidth: style.borderTopWidth,
|
|
34
|
+
boxShadow: style.boxShadow,
|
|
35
|
+
height: style.height,
|
|
36
|
+
width: style.width,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("it is two named sliders inside one named group", async () => {
|
|
41
|
+
const { canvas, canvasElement } =
|
|
42
|
+
await mountStory(Default)
|
|
43
|
+
|
|
44
|
+
const start = expectAgentDrivable(canvas, {
|
|
45
|
+
name: "Clip start",
|
|
46
|
+
role: "slider",
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const end = expectAgentDrivable(canvas, {
|
|
50
|
+
name: "Clip end",
|
|
51
|
+
role: "slider",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// The group is what carries the label the two handles are each an
|
|
55
|
+
// end of. Without it a screen reader announces "Clip start" with
|
|
56
|
+
// nothing saying the two belong together.
|
|
57
|
+
expectAgentDrivable(canvas, {
|
|
58
|
+
name: "Clip",
|
|
59
|
+
role: "group",
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await expect(start).toHaveAttribute(
|
|
63
|
+
"aria-valuenow",
|
|
64
|
+
"600",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
await expect(end).toHaveAttribute("aria-valuenow", "1500")
|
|
68
|
+
|
|
69
|
+
// Each thumb reports the OTHER as its own bound, which is the
|
|
70
|
+
// clamping rule said in the only vocabulary a slider has.
|
|
71
|
+
await expect(start).toHaveAttribute("aria-valuemin", "0")
|
|
72
|
+
|
|
73
|
+
await expect(start).toHaveAttribute(
|
|
74
|
+
"aria-valuemax",
|
|
75
|
+
"1500",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
await expect(end).toHaveAttribute("aria-valuemin", "600")
|
|
79
|
+
|
|
80
|
+
await expect(end).toHaveAttribute("aria-valuemax", "2700")
|
|
81
|
+
|
|
82
|
+
await expectNoAxeViolations(canvasElement)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("each thumb takes the arrow keys on its own", async () => {
|
|
86
|
+
const { canvas } = await mountStory(Interactive)
|
|
87
|
+
|
|
88
|
+
const start = expectAgentDrivable(canvas, {
|
|
89
|
+
name: "Clip start",
|
|
90
|
+
role: "slider",
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const end = expectAgentDrivable(canvas, {
|
|
94
|
+
name: "Clip end",
|
|
95
|
+
role: "slider",
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
start.focus()
|
|
99
|
+
|
|
100
|
+
await userEvent.keyboard("{ArrowRight}")
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(start).toHaveAttribute("aria-valuenow", "630")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// The other end did not move. That is the whole difference between
|
|
107
|
+
// two sliders and one widget with a second handle painted on.
|
|
108
|
+
await expect(end).toHaveAttribute("aria-valuenow", "1500")
|
|
109
|
+
|
|
110
|
+
end.focus()
|
|
111
|
+
|
|
112
|
+
await userEvent.keyboard("{ArrowUp}{ArrowUp}")
|
|
113
|
+
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
expect(end).toHaveAttribute("aria-valuenow", "1560")
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await expect(start).toHaveAttribute(
|
|
119
|
+
"aria-valuenow",
|
|
120
|
+
"630",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Ten steps of 30, from the default `largeStep` of a tenth of the
|
|
124
|
+
// range: a caller should not have to know its own range to get a
|
|
125
|
+
// sensible page key.
|
|
126
|
+
await userEvent.keyboard("{PageUp}")
|
|
127
|
+
|
|
128
|
+
await waitFor(() => {
|
|
129
|
+
expect(end).toHaveAttribute("aria-valuenow", "1830")
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The crossing rule, driven rather than argued: the start thumb walks
|
|
135
|
+
* up into the end thumb, stops **on** it, and is still the start
|
|
136
|
+
* thumb. A widget that swapped them would report the movement on the
|
|
137
|
+
* element that no longer has focus.
|
|
138
|
+
*/
|
|
139
|
+
test("a thumb stops on its partner instead of crossing it", async () => {
|
|
140
|
+
const { canvas, canvasElement } =
|
|
141
|
+
await mountStory(Interactive)
|
|
142
|
+
|
|
143
|
+
const start = expectAgentDrivable(canvas, {
|
|
144
|
+
name: "Clip start",
|
|
145
|
+
role: "slider",
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const end = expectAgentDrivable(canvas, {
|
|
149
|
+
name: "Clip end",
|
|
150
|
+
role: "slider",
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
start.focus()
|
|
154
|
+
|
|
155
|
+
// End means "as far as THIS thumb may go", which is its partner.
|
|
156
|
+
await userEvent.keyboard("{End}")
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(start).toHaveAttribute("aria-valuenow", "1500")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await expect(end).toHaveAttribute("aria-valuenow", "1500")
|
|
163
|
+
|
|
164
|
+
// Two more presses past the partner. A start thumb that reached
|
|
165
|
+
// 1560 would paint a span of negative width, which is invisible
|
|
166
|
+
// rather than wrong-looking.
|
|
167
|
+
await userEvent.keyboard("{ArrowRight}{ArrowRight}")
|
|
168
|
+
|
|
169
|
+
await expect(start).toHaveAttribute(
|
|
170
|
+
"aria-valuenow",
|
|
171
|
+
"1500",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
// The collapsed pair still says where each handle may go.
|
|
175
|
+
await expect(start).toHaveAttribute(
|
|
176
|
+
"aria-valuemax",
|
|
177
|
+
"1500",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
await expect(end).toHaveAttribute("aria-valuemin", "1500")
|
|
181
|
+
|
|
182
|
+
// Focus never moved to the other handle, so the arrow key and the
|
|
183
|
+
// value that changed still belong to the same element.
|
|
184
|
+
await expect(document.activeElement).toBe(start)
|
|
185
|
+
|
|
186
|
+
// Collapsed is a state a user can reach, so it is a state that has
|
|
187
|
+
// to audit clean.
|
|
188
|
+
await expectNoAxeViolations(canvasElement)
|
|
189
|
+
|
|
190
|
+
await userEvent.keyboard("{ArrowLeft}")
|
|
191
|
+
|
|
192
|
+
await waitFor(() => {
|
|
193
|
+
expect(start).toHaveAttribute("aria-valuenow", "1470")
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* A press anywhere on the bar picks up the **nearer** thumb and moves
|
|
199
|
+
* it there. The bar is the pointer target, exactly as it is on
|
|
200
|
+
* `Slider` — a two-thumb range whose only pointer targets were its
|
|
201
|
+
* 16px handles would be a control nobody can aim at.
|
|
202
|
+
*
|
|
203
|
+
* Driven as a real pointer rather than as dispatched `PointerEvent`s,
|
|
204
|
+
* for `ReorderList`'s reason: `setPointerCapture` rejects an id that
|
|
205
|
+
* matches no active pointer, so a synthetic sequence dies inside the
|
|
206
|
+
* handler before a listener is ever registered. The whole gesture goes
|
|
207
|
+
* in ONE `userEvent.pointer` call, because the pressed state lives in
|
|
208
|
+
* that call rather than in the default instance.
|
|
209
|
+
*/
|
|
210
|
+
test("a press on the bar moves the nearer thumb", async () => {
|
|
211
|
+
const { canvas } = await mountStory(Interactive)
|
|
212
|
+
|
|
213
|
+
const group = expectAgentDrivable(canvas, {
|
|
214
|
+
name: "Clip",
|
|
215
|
+
role: "group",
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const start = expectAgentDrivable(canvas, {
|
|
219
|
+
name: "Clip start",
|
|
220
|
+
role: "slider",
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const end = expectAgentDrivable(canvas, {
|
|
224
|
+
name: "Clip end",
|
|
225
|
+
role: "slider",
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const at = pointerAt(group)
|
|
229
|
+
|
|
230
|
+
await userEvent.pointer([
|
|
231
|
+
{
|
|
232
|
+
coords: at(0.9),
|
|
233
|
+
keys: "[MouseLeft>]",
|
|
234
|
+
target: group,
|
|
235
|
+
},
|
|
236
|
+
{ keys: "[/MouseLeft]" },
|
|
237
|
+
])
|
|
238
|
+
|
|
239
|
+
// 90% of a 0–2700 range, on a step of 30.
|
|
240
|
+
await waitFor(() => {
|
|
241
|
+
expect(end).toHaveAttribute("aria-valuenow", "2430")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
await expect(start).toHaveAttribute(
|
|
245
|
+
"aria-valuenow",
|
|
246
|
+
"600",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
// Focus follows the grab, so a pointer user reaching for the arrow
|
|
250
|
+
// keys moves the handle they were just dragging.
|
|
251
|
+
await expect(document.activeElement).toBe(end)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The pointer half of the crossing rule, and the reason the dragged
|
|
256
|
+
* thumb is remembered rather than recomputed: halfway through this
|
|
257
|
+
* gesture the *nearer* thumb becomes the other one, and the handle
|
|
258
|
+
* under the pointer must not change.
|
|
259
|
+
*/
|
|
260
|
+
test("a drag keeps the thumb it picked up, and stops it on its partner", async () => {
|
|
261
|
+
const { canvas } = await mountStory(Interactive)
|
|
262
|
+
|
|
263
|
+
const group = expectAgentDrivable(canvas, {
|
|
264
|
+
name: "Clip",
|
|
265
|
+
role: "group",
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const start = expectAgentDrivable(canvas, {
|
|
269
|
+
name: "Clip start",
|
|
270
|
+
role: "slider",
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const end = expectAgentDrivable(canvas, {
|
|
274
|
+
name: "Clip end",
|
|
275
|
+
role: "slider",
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const at = pointerAt(group)
|
|
279
|
+
|
|
280
|
+
await userEvent.pointer([
|
|
281
|
+
{
|
|
282
|
+
coords: at(0.9),
|
|
283
|
+
keys: "[MouseLeft>]",
|
|
284
|
+
target: group,
|
|
285
|
+
},
|
|
286
|
+
{ coords: at(0.5) },
|
|
287
|
+
{ coords: at(0.05) },
|
|
288
|
+
{ keys: "[/MouseLeft]" },
|
|
289
|
+
])
|
|
290
|
+
|
|
291
|
+
// Dragged well below the start thumb, and stopped on it.
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(end).toHaveAttribute("aria-valuenow", "600")
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await expect(start).toHaveAttribute(
|
|
297
|
+
"aria-valuenow",
|
|
298
|
+
"600",
|
|
299
|
+
)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test("a disabled range is out of the tab order and does not move", async () => {
|
|
303
|
+
const { canvas, canvasElement } =
|
|
304
|
+
await mountStory(AllStates)
|
|
305
|
+
|
|
306
|
+
const start = expectAgentDrivable(canvas, {
|
|
307
|
+
name: "Disabled start",
|
|
308
|
+
role: "slider",
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
await expect(start).toHaveAttribute(
|
|
312
|
+
"aria-disabled",
|
|
313
|
+
"true",
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
await expect(start).toHaveAttribute("tabindex", "-1")
|
|
317
|
+
|
|
318
|
+
// The row, not the thumb, carries `pointer-events: none` — the
|
|
319
|
+
// whole control is inert, including the bar a press would land on.
|
|
320
|
+
const group = canvas.getByRole("group", {
|
|
321
|
+
name: "Disabled",
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
await expect(getComputedStyle(group).pointerEvents).toBe(
|
|
325
|
+
"none",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
start.focus()
|
|
329
|
+
|
|
330
|
+
await userEvent.keyboard("{ArrowRight}")
|
|
331
|
+
|
|
332
|
+
await expect(start).toHaveAttribute("aria-valuenow", "30")
|
|
333
|
+
|
|
334
|
+
await expectNoAxeViolations(canvasElement)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Read-only is the state that is easy to collapse into `disabled`,
|
|
339
|
+
* and they are different answers: this one is still focusable and
|
|
340
|
+
* still full contrast, because both ends of the span are worth
|
|
341
|
+
* reading even though neither can be changed.
|
|
342
|
+
*/
|
|
343
|
+
test("a read-only range is announced, focusable, and does not move", async () => {
|
|
344
|
+
const { canvas, canvasElement } =
|
|
345
|
+
await mountStory(AllStates)
|
|
346
|
+
|
|
347
|
+
const end = expectAgentDrivable(canvas, {
|
|
348
|
+
name: "Read-only end",
|
|
349
|
+
role: "slider",
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
await expect(end).toHaveAttribute("aria-readonly", "true")
|
|
353
|
+
|
|
354
|
+
await expect(end).toHaveAttribute("tabindex", "0")
|
|
355
|
+
|
|
356
|
+
await expect(end).not.toHaveAttribute("aria-disabled")
|
|
357
|
+
|
|
358
|
+
end.focus()
|
|
359
|
+
|
|
360
|
+
await userEvent.keyboard("{ArrowLeft}")
|
|
361
|
+
|
|
362
|
+
await expect(end).toHaveAttribute("aria-valuenow", "70")
|
|
363
|
+
|
|
364
|
+
await expectNoAxeViolations(canvasElement)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* `valueFormat` is the whole reason a media timeline can live in an
|
|
369
|
+
* app that never taught the library what a timecode is. Without it a
|
|
370
|
+
* clip announces "600", which is a number of seconds nobody has.
|
|
371
|
+
*/
|
|
372
|
+
test("valueFormat writes aria-valuetext and the shown span", async () => {
|
|
373
|
+
const { canvas } = await mountStory(AllStates)
|
|
374
|
+
|
|
375
|
+
const start = expectAgentDrivable(canvas, {
|
|
376
|
+
name: "Section start",
|
|
377
|
+
role: "slider",
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
await expect(start).toHaveAttribute(
|
|
381
|
+
"aria-valuetext",
|
|
382
|
+
"10:00",
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
// The raw number is still there for anything that computes with it.
|
|
386
|
+
await expect(start).toHaveAttribute(
|
|
387
|
+
"aria-valuenow",
|
|
388
|
+
"600",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
// "to", not an en dash: a default is words. Two cells in the story
|
|
392
|
+
// show this span, so the query is the plural one.
|
|
393
|
+
await expect(
|
|
394
|
+
canvas.getAllByText("10:00 to 25:00")[0],
|
|
395
|
+
).toBeVisible()
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Tick marks are a picture of the app's own chapter list. Each thumb
|
|
400
|
+
* already announces its own position, so a screen reader reading the
|
|
401
|
+
* marks would be reading the decoration twice.
|
|
402
|
+
*/
|
|
403
|
+
test("tick labels are drawn and not announced", async () => {
|
|
404
|
+
const { canvas } = await mountStory(AllStates)
|
|
405
|
+
|
|
406
|
+
const chapter = canvas.getByText("Act one")
|
|
407
|
+
|
|
408
|
+
await expect(chapter).toBeVisible()
|
|
409
|
+
|
|
410
|
+
await expect(
|
|
411
|
+
chapter.closest("[aria-hidden='true']"),
|
|
412
|
+
).not.toBeNull()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* The shared bar, asserted rather than trusted.
|
|
417
|
+
*
|
|
418
|
+
* `Slider` and `RangeSlider` are separate components and paint one
|
|
419
|
+
* control, through `../sliderStyles.ts`. "They look the same" written
|
|
420
|
+
* as two class strings is a promise that survives exactly one edit, so
|
|
421
|
+
* this compares **computed** styles — the version of the claim a
|
|
422
|
+
* class-name refactor cannot quietly break.
|
|
423
|
+
*/
|
|
424
|
+
test("a range thumb is a slider thumb", async () => {
|
|
425
|
+
const { canvas } = await mountStory(BesideTheSlider)
|
|
426
|
+
|
|
427
|
+
const slider = expectAgentDrivable(canvas, {
|
|
428
|
+
name: "Position",
|
|
429
|
+
role: "slider",
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// `Slider`'s thumb is decoration hanging off the track, because
|
|
433
|
+
// there the TRACK is the widget. It is the one element in this
|
|
434
|
+
// package that has to be reached structurally: giving it a role or
|
|
435
|
+
// a name would be the bug the component exists to avoid.
|
|
436
|
+
const sliderThumb = slider.querySelector(
|
|
437
|
+
':scope > span[aria-hidden="true"]',
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if (!sliderThumb) {
|
|
441
|
+
throw new Error("the slider drew no thumb")
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const rangeThumb = expectAgentDrivable(canvas, {
|
|
445
|
+
name: "Section start",
|
|
446
|
+
role: "slider",
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
expect(readThumbStyles(rangeThumb)).toEqual(
|
|
450
|
+
readThumbStyles(sliderThumb),
|
|
451
|
+
)
|
|
452
|
+
})
|