@charcuterie/ui 4.26.2 → 4.27.1

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.
@@ -0,0 +1,385 @@
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 "./ColorPicker.stories.tsx"
9
+ import { parseHexColor } from "./parseHexColor.ts"
10
+
11
+ const {
12
+ AllStates,
13
+ AllVariants,
14
+ CustomPanel,
15
+ Interactive,
16
+ Playground,
17
+ Responsive,
18
+ } = composeStories(stories)
19
+
20
+ const openInteractive = async () => {
21
+ const mounted = await mountStory(Interactive)
22
+
23
+ await userEvent.click(
24
+ expectAgentDrivable(mounted.canvas, {
25
+ name: "Interactive color: Blue",
26
+ role: "button",
27
+ }),
28
+ )
29
+
30
+ const input = expectAgentDrivable(mounted.body, {
31
+ role: "combobox",
32
+ })
33
+
34
+ await waitFor(() => {
35
+ expect(input).toHaveFocus()
36
+ })
37
+
38
+ return { ...mounted, input }
39
+ }
40
+
41
+ test("the trigger names the preset and shows its swatch", async () => {
42
+ const { canvas, canvasElement } =
43
+ await mountStory(Playground)
44
+
45
+ const trigger = expectAgentDrivable(canvas, {
46
+ name: "Color: Hot Pink",
47
+ role: "button",
48
+ })
49
+
50
+ await expect(trigger).toHaveTextContent("Hot Pink")
51
+
52
+ const swatchDot = trigger.querySelector(
53
+ '[role="img"] > span',
54
+ ) as HTMLElement
55
+
56
+ await expect(
57
+ getComputedStyle(swatchDot).backgroundColor,
58
+ ).toBe("rgb(255, 0, 110)")
59
+
60
+ await expectNoAxeViolations(canvasElement)
61
+ })
62
+
63
+ test("typing a name narrows the list to that family", async () => {
64
+ const { body, input } = await openInteractive()
65
+
66
+ await userEvent.type(input, "pink")
67
+
68
+ await waitFor(() => {
69
+ const names = body
70
+ .getAllByRole("option")
71
+ .map((option: HTMLElement) =>
72
+ // The tick is drawn in every row and shown on the chosen one.
73
+ option.textContent?.replace("✓", "").trim(),
74
+ )
75
+
76
+ // "Hot Pink" by its name, the rest by their `keywords`.
77
+ expect(names).toStrictEqual([
78
+ "Raspberry",
79
+ "Hot Pink",
80
+ "Blush",
81
+ "Bubblegum",
82
+ "Cotton Candy",
83
+ "Custom color…",
84
+ ])
85
+ })
86
+ })
87
+
88
+ test("a hex code finds its preset", async () => {
89
+ const { body, input } = await openInteractive()
90
+
91
+ await userEvent.type(input, "#ff69")
92
+
93
+ await waitFor(() => {
94
+ body.getByRole("option", { name: "Bubblegum" })
95
+ })
96
+ })
97
+
98
+ test("choosing a preset changes the value and the trigger", async () => {
99
+ const { body, canvas } = await openInteractive()
100
+
101
+ await userEvent.click(
102
+ body.getByRole("option", { name: "Raspberry" }),
103
+ )
104
+
105
+ await waitFor(() => {
106
+ expectAgentDrivable(canvas, {
107
+ name: "Interactive color: Raspberry",
108
+ role: "button",
109
+ })
110
+ })
111
+
112
+ await expect(
113
+ canvas.getByText("Value: #DB2777"),
114
+ ).toBeInTheDocument()
115
+ })
116
+
117
+ test("a custom color is applied from the hex field and the trigger reads its code", async () => {
118
+ const { body, canvas } = await openInteractive()
119
+
120
+ await userEvent.click(
121
+ body.getByRole("option", { name: "Custom color…" }),
122
+ )
123
+
124
+ const hexInput = expectAgentDrivable(canvas, {
125
+ name: "Hex code",
126
+ role: "textbox",
127
+ })
128
+
129
+ await waitFor(() => {
130
+ expect(hexInput).toHaveFocus()
131
+ })
132
+
133
+ await userEvent.clear(hexInput)
134
+ await userEvent.type(hexInput, "c2185b{Enter}")
135
+
136
+ await waitFor(() => {
137
+ expectAgentDrivable(canvas, {
138
+ name: "Interactive color: #C2185B",
139
+ role: "button",
140
+ })
141
+ })
142
+
143
+ await expect(
144
+ canvas.getByText("Value: #C2185B"),
145
+ ).toBeInTheDocument()
146
+
147
+ // Applying closes the panel and returns focus to the trigger.
148
+ await expect(
149
+ canvas.queryByRole("textbox", { name: "Hex code" }),
150
+ ).toBeNull()
151
+ })
152
+
153
+ test("a hex typed into the search seeds the custom panel", async () => {
154
+ const { body, canvas, input } = await openInteractive()
155
+
156
+ await userEvent.type(input, "7b1fa2")
157
+
158
+ await userEvent.click(
159
+ body.getByRole("option", {
160
+ name: "Custom color #7B1FA2…",
161
+ }),
162
+ )
163
+
164
+ await expect(
165
+ expectAgentDrivable(canvas, {
166
+ name: "Hex code",
167
+ role: "textbox",
168
+ }),
169
+ ).toHaveValue("#7B1FA2")
170
+ })
171
+
172
+ test("the app's rule blocks a custom color with its own message", async () => {
173
+ const { canvas, canvasElement } =
174
+ await mountStory(CustomPanel)
175
+
176
+ await CustomPanel.play?.({
177
+ canvasElement,
178
+ } as never)
179
+
180
+ const hexInput = expectAgentDrivable(canvas, {
181
+ name: "Hex code",
182
+ role: "textbox",
183
+ })
184
+
185
+ await expect(hexInput).toHaveAttribute(
186
+ "aria-invalid",
187
+ "true",
188
+ )
189
+
190
+ await expect(
191
+ canvas.getByText(/Gray, white, and black/),
192
+ ).toBeInTheDocument()
193
+
194
+ await expect(
195
+ expectAgentDrivable(canvas, {
196
+ name: "Use color",
197
+ role: "button",
198
+ }),
199
+ ).toBeDisabled()
200
+
201
+ await userEvent.clear(hexInput)
202
+ await userEvent.type(hexInput, "E040FB")
203
+
204
+ await waitFor(() => {
205
+ expect(hexInput).not.toHaveAttribute("aria-invalid")
206
+ })
207
+
208
+ await expect(
209
+ expectAgentDrivable(canvas, {
210
+ name: "Use color",
211
+ role: "button",
212
+ }),
213
+ ).toBeEnabled()
214
+
215
+ await expectNoAxeViolations(canvasElement)
216
+ })
217
+
218
+ test("an unreadable code explains itself and cannot be applied", async () => {
219
+ const { canvas, canvasElement } =
220
+ await mountStory(CustomPanel)
221
+
222
+ await CustomPanel.play?.({
223
+ canvasElement,
224
+ } as never)
225
+
226
+ const hexInput = expectAgentDrivable(canvas, {
227
+ name: "Hex code",
228
+ role: "textbox",
229
+ })
230
+
231
+ await userEvent.clear(hexInput)
232
+ await userEvent.type(hexInput, "12345")
233
+
234
+ await expect(
235
+ canvas.getByText(/three or six hex digits/),
236
+ ).toBeInTheDocument()
237
+
238
+ await expect(
239
+ expectAgentDrivable(canvas, {
240
+ name: "Use color",
241
+ role: "button",
242
+ }),
243
+ ).toBeDisabled()
244
+ })
245
+
246
+ test("the color wheel follows the hex field", async () => {
247
+ const { canvas, canvasElement } =
248
+ await mountStory(CustomPanel)
249
+
250
+ await CustomPanel.play?.({
251
+ canvasElement,
252
+ } as never)
253
+
254
+ const hexInput = expectAgentDrivable(canvas, {
255
+ name: "Hex code",
256
+ role: "textbox",
257
+ })
258
+
259
+ await userEvent.clear(hexInput)
260
+ await userEvent.type(hexInput, "#0af")
261
+
262
+ await expect(
263
+ canvas.getByLabelText("Rule color: color wheel"),
264
+ ).toHaveValue("#00aaff")
265
+ })
266
+
267
+ test("Escape and Cancel drop the draft and keep the value", async () => {
268
+ const { canvas, canvasElement } =
269
+ await mountStory(CustomPanel)
270
+
271
+ await CustomPanel.play?.({
272
+ canvasElement,
273
+ } as never)
274
+
275
+ const hexInput = expectAgentDrivable(canvas, {
276
+ name: "Hex code",
277
+ role: "textbox",
278
+ })
279
+
280
+ await userEvent.clear(hexInput)
281
+ await userEvent.type(hexInput, "E040FB{Escape}")
282
+
283
+ await expect(
284
+ canvas.queryByRole("textbox", { name: "Hex code" }),
285
+ ).toBeNull()
286
+
287
+ await expect(
288
+ canvas.getByText("Value: #808080"),
289
+ ).toBeInTheDocument()
290
+ })
291
+
292
+ test("every state is named and has no axe violations", async () => {
293
+ const { canvas, canvasElement } =
294
+ await mountStory(AllStates)
295
+
296
+ expectAgentDrivable(canvas, {
297
+ name: "Preset color: Bubblegum",
298
+ role: "button",
299
+ })
300
+ expectAgentDrivable(canvas, {
301
+ name: "Custom color: #C2185B",
302
+ role: "button",
303
+ })
304
+ expectAgentDrivable(canvas, {
305
+ name: "Empty color: Choose a color",
306
+ role: "button",
307
+ })
308
+
309
+ await expect(
310
+ expectAgentDrivable(canvas, {
311
+ name: "Disabled color: Purple",
312
+ role: "button",
313
+ }),
314
+ ).toBeDisabled()
315
+
316
+ await expectNoAxeViolations(canvasElement)
317
+ })
318
+
319
+ test("presets only offers no custom row", async () => {
320
+ const { body, canvas } = await mountStory(AllStates)
321
+
322
+ await userEvent.click(
323
+ expectAgentDrivable(canvas, {
324
+ name: "Presets-only color: Green",
325
+ role: "button",
326
+ }),
327
+ )
328
+
329
+ await waitFor(() => {
330
+ expect(
331
+ body.getAllByRole("option").length,
332
+ ).toBeGreaterThan(0)
333
+ })
334
+
335
+ await expect(
336
+ body.queryByRole("option", { name: "Custom color…" }),
337
+ ).toBeNull()
338
+ })
339
+
340
+ test("every size renders a named trigger", async () => {
341
+ const { canvas, canvasElement } =
342
+ await mountStory(AllVariants)
343
+
344
+ for (const size of ["sm", "md", "lg"]) {
345
+ expectAgentDrivable(canvas, {
346
+ name: `Color ${size}: Blue`,
347
+ role: "button",
348
+ })
349
+ }
350
+
351
+ await expectNoAxeViolations(canvasElement)
352
+ })
353
+
354
+ test("a stretched trigger keeps its chevron inside", async () => {
355
+ const { canvas } = await mountStory(Responsive)
356
+
357
+ for (const trigger of canvas.getAllByRole("button", {
358
+ name: /^Color at .*: Cotton Candy$/,
359
+ })) {
360
+ const triggerBox = trigger.getBoundingClientRect()
361
+ const chevronBox = (
362
+ trigger.querySelector("svg") as SVGElement
363
+ ).getBoundingClientRect()
364
+
365
+ await expect(trigger.scrollWidth).toBe(
366
+ trigger.clientWidth,
367
+ )
368
+ await expect(chevronBox.right).toBeLessThanOrEqual(
369
+ triggerBox.right,
370
+ )
371
+ }
372
+ })
373
+
374
+ test.each([
375
+ ["3a86ff", "#3A86FF"],
376
+ ["#3A86FF", "#3A86FF"],
377
+ [" #38f ", "#3388FF"],
378
+ ["abc", "#AABBCC"],
379
+ ["12345", null],
380
+ ["#GGGGGG", null],
381
+ ["#3A86FF80", null],
382
+ ["", null],
383
+ ])("parseHexColor(%j) is %j", (text, expected) => {
384
+ expect(parseHexColor(text)).toBe(expected)
385
+ })