@astryxdesign/core 0.7.2-canary.fbc03f7 → 0.7.2-canary.fdd384f

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.
Files changed (58) hide show
  1. package/dist/AppShell/AppShell.d.ts.map +1 -1
  2. package/dist/AppShell/AppShell.js +40 -1
  3. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  4. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  5. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  6. package/dist/Heading/Heading.d.ts.map +1 -1
  7. package/dist/Heading/Heading.js +4 -2
  8. package/dist/MobileNav/MobileNav.d.ts +9 -0
  9. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  10. package/dist/MobileNav/MobileNav.js +30 -3
  11. package/dist/SideNav/SideNavItem.d.ts +8 -1
  12. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  13. package/dist/SideNav/SideNavItem.js +71 -9
  14. package/dist/Text/Text.d.ts.map +1 -1
  15. package/dist/Text/Text.js +4 -2
  16. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  17. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  18. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  19. package/dist/TopNav/TopNavMenu.js +3 -3
  20. package/dist/TreeList/TreeList.d.ts.map +1 -1
  21. package/dist/TreeList/TreeList.js +1 -0
  22. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  23. package/dist/TreeList/TreeListBranches.js +2 -2
  24. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  25. package/dist/TreeList/TreeListItem.js +57 -9
  26. package/dist/astryx.css +11 -1
  27. package/package.json +3 -3
  28. package/src/AppShell/AppShell.tsx +45 -1
  29. package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
  30. package/src/Card/Card.doc.mjs +1 -1
  31. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  32. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  33. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  34. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
  35. package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
  36. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
  37. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -1
  38. package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
  39. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
  40. package/src/Heading/Heading.test.tsx +57 -0
  41. package/src/Heading/Heading.tsx +6 -2
  42. package/src/MobileNav/MobileNav.tsx +30 -3
  43. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
  44. package/src/SideNav/SideNav.test.tsx +179 -0
  45. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  46. package/src/SideNav/SideNavItem.tsx +96 -17
  47. package/src/Text/Text.test.tsx +57 -0
  48. package/src/Text/Text.tsx +6 -2
  49. package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
  50. package/src/TopNav/TopNavMegaMenu.tsx +7 -3
  51. package/src/TopNav/TopNavMenu.test.tsx +19 -0
  52. package/src/TopNav/TopNavMenu.tsx +11 -3
  53. package/src/TreeList/TreeList.doc.mjs +1 -0
  54. package/src/TreeList/TreeList.test.tsx +327 -6
  55. package/src/TreeList/TreeList.tsx +5 -1
  56. package/src/TreeList/TreeListBranches.tsx +6 -0
  57. package/src/TreeList/TreeListItem.tsx +88 -13
  58. package/src/theme/derivedVarRegistry.test.ts +4 -0
@@ -0,0 +1,438 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ContextMenuAudit.a11y.chromium.spec.ts
5
+ * @input Exact-head ContextMenu stories across pointer/touch presentation, state, theme, direction, and viewport
6
+ * @output Screenshots and fail-closed sensor receipts for the ContextMenu whole-component audit
7
+ * @position Component-owned Night Watch browser evidence; CI runs it only for a ContextMenu audit pull request
8
+ */
9
+
10
+ import {createHash} from 'node:crypto';
11
+ import {execFileSync} from 'node:child_process';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import {expect, test, type Locator, type Page} from '@playwright/test';
15
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
16
+ import {
17
+ DEFAULT_STORYBOOK_DIR,
18
+ serveStorybook,
19
+ type StaticServer,
20
+ } from '@astryxdesign/a11y-spec/storybook';
21
+
22
+ const OUTPUT = path.resolve('context-menu-audit-evidence');
23
+ const WIDE = {width: 1024, height: 768};
24
+ const NARROW = {width: 320, height: 720};
25
+
26
+ type Mode = 'light' | 'dark';
27
+ type Direction = 'ltr' | 'rtl';
28
+ type Theme = 'neutral' | 'butter';
29
+ type Surface = 'menu' | 'dialog';
30
+ type AuditCase = {
31
+ key: string;
32
+ storyId: string;
33
+ mode: Mode;
34
+ direction: Direction;
35
+ theme: Theme;
36
+ viewport: {width: number; height: number};
37
+ surface: Surface;
38
+ triggerText?: string;
39
+ menuName?: string;
40
+ expectedState?: 'default' | 'disabled' | 'sections' | 'selectable';
41
+ };
42
+
43
+ const cases: AuditCase[] = [
44
+ {
45
+ key: 'pointer-neutral-light',
46
+ storyId: 'core-contextmenu--pointer-presentation',
47
+ mode: 'light',
48
+ direction: 'ltr',
49
+ theme: 'neutral',
50
+ viewport: WIDE,
51
+ surface: 'menu',
52
+ menuName: 'Document actions',
53
+ },
54
+ {
55
+ key: 'pointer-neutral-dark',
56
+ storyId: 'core-contextmenu--pointer-presentation',
57
+ mode: 'dark',
58
+ direction: 'ltr',
59
+ theme: 'neutral',
60
+ viewport: WIDE,
61
+ surface: 'menu',
62
+ menuName: 'Document actions',
63
+ },
64
+ {
65
+ key: 'pointer-butter-light',
66
+ storyId: 'core-contextmenu--pointer-presentation',
67
+ mode: 'light',
68
+ direction: 'ltr',
69
+ theme: 'butter',
70
+ viewport: WIDE,
71
+ surface: 'menu',
72
+ menuName: 'Document actions',
73
+ },
74
+ {
75
+ key: 'pointer-butter-dark',
76
+ storyId: 'core-contextmenu--pointer-presentation',
77
+ mode: 'dark',
78
+ direction: 'ltr',
79
+ theme: 'butter',
80
+ viewport: WIDE,
81
+ surface: 'menu',
82
+ menuName: 'Document actions',
83
+ },
84
+ {
85
+ key: 'pointer-rtl-light',
86
+ storyId: 'core-contextmenu--pointer-presentation',
87
+ mode: 'light',
88
+ direction: 'rtl',
89
+ theme: 'neutral',
90
+ viewport: WIDE,
91
+ surface: 'menu',
92
+ menuName: 'Document actions',
93
+ },
94
+ {
95
+ key: 'pointer-rtl-dark',
96
+ storyId: 'core-contextmenu--pointer-presentation',
97
+ mode: 'dark',
98
+ direction: 'rtl',
99
+ theme: 'neutral',
100
+ viewport: WIDE,
101
+ surface: 'menu',
102
+ menuName: 'Document actions',
103
+ },
104
+ {
105
+ key: 'pointer-narrow-light',
106
+ storyId: 'core-contextmenu--pointer-presentation',
107
+ mode: 'light',
108
+ direction: 'ltr',
109
+ theme: 'neutral',
110
+ viewport: NARROW,
111
+ surface: 'menu',
112
+ menuName: 'Document actions',
113
+ },
114
+ {
115
+ key: 'pointer-narrow-dark',
116
+ storyId: 'core-contextmenu--pointer-presentation',
117
+ mode: 'dark',
118
+ direction: 'ltr',
119
+ theme: 'neutral',
120
+ viewport: NARROW,
121
+ surface: 'menu',
122
+ menuName: 'Document actions',
123
+ },
124
+ {
125
+ key: 'disabled-item-light',
126
+ storyId: 'core-contextmenu--with-disabled-items',
127
+ mode: 'light',
128
+ direction: 'ltr',
129
+ theme: 'neutral',
130
+ viewport: WIDE,
131
+ surface: 'menu',
132
+ triggerText: 'Right-click this area',
133
+ expectedState: 'disabled',
134
+ },
135
+ {
136
+ key: 'sections-dark',
137
+ storyId: 'core-contextmenu--with-sections',
138
+ mode: 'dark',
139
+ direction: 'ltr',
140
+ theme: 'neutral',
141
+ viewport: WIDE,
142
+ surface: 'menu',
143
+ triggerText: 'Right-click for grouped actions',
144
+ expectedState: 'sections',
145
+ },
146
+ {
147
+ key: 'selectable-light',
148
+ storyId: 'core-contextmenu--with-selectable-items',
149
+ mode: 'light',
150
+ direction: 'ltr',
151
+ theme: 'neutral',
152
+ viewport: WIDE,
153
+ surface: 'menu',
154
+ triggerText: 'Right-click for selectable items',
155
+ expectedState: 'selectable',
156
+ },
157
+ {
158
+ key: 'selectable-dark',
159
+ storyId: 'core-contextmenu--with-selectable-items',
160
+ mode: 'dark',
161
+ direction: 'ltr',
162
+ theme: 'neutral',
163
+ viewport: WIDE,
164
+ surface: 'menu',
165
+ triggerText: 'Right-click for selectable items',
166
+ expectedState: 'selectable',
167
+ },
168
+ {
169
+ key: 'sheet-light',
170
+ storyId: 'core-contextmenu--bottom-sheet-presentation',
171
+ mode: 'light',
172
+ direction: 'ltr',
173
+ theme: 'neutral',
174
+ viewport: NARROW,
175
+ surface: 'dialog',
176
+ },
177
+ {
178
+ key: 'sheet-dark',
179
+ storyId: 'core-contextmenu--bottom-sheet-presentation',
180
+ mode: 'dark',
181
+ direction: 'ltr',
182
+ theme: 'neutral',
183
+ viewport: NARROW,
184
+ surface: 'dialog',
185
+ },
186
+ {
187
+ key: 'sheet-rtl',
188
+ storyId: 'core-contextmenu--bottom-sheet-presentation',
189
+ mode: 'light',
190
+ direction: 'rtl',
191
+ theme: 'neutral',
192
+ viewport: NARROW,
193
+ surface: 'dialog',
194
+ },
195
+ {
196
+ key: 'inline-light',
197
+ storyId: 'core-contextmenu--inline-trigger',
198
+ mode: 'light',
199
+ direction: 'ltr',
200
+ theme: 'neutral',
201
+ viewport: WIDE,
202
+ surface: 'menu',
203
+ triggerText: 'T123',
204
+ menuName: 'Task actions',
205
+ },
206
+ {
207
+ key: 'inline-dark',
208
+ storyId: 'core-contextmenu--inline-trigger',
209
+ mode: 'dark',
210
+ direction: 'ltr',
211
+ theme: 'neutral',
212
+ viewport: WIDE,
213
+ surface: 'menu',
214
+ triggerText: 'T123',
215
+ menuName: 'Task actions',
216
+ },
217
+ {
218
+ key: 'scroll-container-light',
219
+ storyId: 'core-contextmenu--in-scroll-container',
220
+ mode: 'light',
221
+ direction: 'ltr',
222
+ theme: 'neutral',
223
+ viewport: WIDE,
224
+ surface: 'menu',
225
+ triggerText: 'Right-click this area',
226
+ },
227
+ ];
228
+
229
+ test.describe.configure({mode: 'serial', retries: 0});
230
+ let server: StaticServer;
231
+ let checkoutSha: string;
232
+ let storybookSha: string;
233
+
234
+ function storyUrl(item: AuditCase) {
235
+ return `${server.origin}/iframe.html?id=${item.storyId}&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
236
+ }
237
+
238
+ async function settle(page: Page) {
239
+ await holdMotionStill(page);
240
+ await page.evaluate(async () => document.fonts.ready);
241
+ await page.waitForTimeout(40);
242
+ }
243
+
244
+ async function openSurface(
245
+ page: Page,
246
+ item: AuditCase,
247
+ ): Promise<{surface: Locator; pointer: {x: number; y: number}}> {
248
+ const trigger =
249
+ item.storyId === 'core-contextmenu--pointer-presentation'
250
+ ? page.getByTestId('pointer-context-menu')
251
+ : item.storyId === 'core-contextmenu--bottom-sheet-presentation'
252
+ ? page.getByTestId('bottom-sheet-context-menu')
253
+ : item.triggerText
254
+ ? page.getByText(item.triggerText, {exact: true}).first()
255
+ : null;
256
+ if (trigger === null) {
257
+ throw new Error(`No trigger configured for ${item.key}`);
258
+ }
259
+ await trigger.scrollIntoViewIfNeeded();
260
+ const triggerBox = await trigger.boundingBox();
261
+ if (triggerBox === null) {
262
+ throw new Error(`Trigger has no rendered box for ${item.key}`);
263
+ }
264
+ const pointer = {
265
+ x: triggerBox.x + Math.min(40, triggerBox.width / 2),
266
+ y: triggerBox.y + Math.min(40, triggerBox.height / 2),
267
+ };
268
+ await page.mouse.click(pointer.x, pointer.y, {button: 'right'});
269
+ const surface =
270
+ item.surface === 'dialog'
271
+ ? page.getByRole('dialog').last()
272
+ : item.menuName
273
+ ? page.getByRole('menu', {name: item.menuName}).last()
274
+ : page.getByRole('menu').last();
275
+ await expect(surface).toBeVisible();
276
+ return {surface, pointer};
277
+ }
278
+
279
+ test.beforeAll(async () => {
280
+ fs.rmSync(OUTPUT, {recursive: true, force: true});
281
+ fs.mkdirSync(OUTPUT, {recursive: true});
282
+ checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
283
+ encoding: 'utf8',
284
+ }).trim();
285
+ const expected = process.env.ASTRYX_HEAD_SHA;
286
+ if (expected && expected !== checkoutSha) {
287
+ throw new Error(
288
+ `checkout ${checkoutSha} does not match expected head ${expected}`,
289
+ );
290
+ }
291
+ server = await serveStorybook(
292
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
293
+ );
294
+ const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
295
+ if (!response.ok) {
296
+ throw new Error(`Storybook build has no SHA stamp (${response.status})`);
297
+ }
298
+ storybookSha = (await response.text()).trim();
299
+ if (storybookSha !== checkoutSha) {
300
+ throw new Error(
301
+ `Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
302
+ );
303
+ }
304
+ });
305
+
306
+ test.afterAll(async () => {
307
+ await server?.close();
308
+ });
309
+
310
+ for (const item of cases) {
311
+ test(item.key, async ({browser}) => {
312
+ const context = await browser.newContext({
313
+ viewport: item.viewport,
314
+ colorScheme: item.mode,
315
+ reducedMotion: 'reduce',
316
+ });
317
+ const page = await context.newPage();
318
+ const pageErrors: string[] = [];
319
+ page.on('pageerror', error => pageErrors.push(String(error)));
320
+ await page.goto(storyUrl(item), {waitUntil: 'load'});
321
+ await page.locator('#storybook-root').waitFor();
322
+ const {surface, pointer} = await openSurface(page, item);
323
+ await settle(page);
324
+
325
+ const menuItems = page.locator(
326
+ '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]',
327
+ );
328
+ if (item.surface === 'menu') {
329
+ await expect(menuItems.first()).toBeVisible();
330
+ } else {
331
+ await expect(surface.getByRole('button').first()).toBeVisible();
332
+ }
333
+ if (item.expectedState === 'disabled') {
334
+ await expect(
335
+ page.locator('[role="menuitem"][aria-disabled="true"]'),
336
+ ).toHaveCount(1);
337
+ }
338
+ if (item.expectedState === 'sections') {
339
+ await expect(page.getByRole('group')).toHaveCount(2);
340
+ }
341
+ if (item.expectedState === 'selectable') {
342
+ await expect(page.getByRole('menuitemcheckbox')).toHaveCount(2);
343
+ await expect(page.getByRole('menuitemradio')).toHaveCount(3);
344
+ await expect(
345
+ page.locator('[role="menuitemradio"][aria-checked="true"]'),
346
+ ).toHaveCount(1);
347
+ }
348
+
349
+ const observed = await surface.evaluate(element => {
350
+ const box = element.getBoundingClientRect();
351
+ return {
352
+ theme: document
353
+ .querySelector('[data-astryx-theme]')
354
+ ?.getAttribute('data-astryx-theme'),
355
+ mode: document.documentElement.getAttribute('data-theme'),
356
+ direction: getComputedStyle(element).direction,
357
+ viewport: {width: innerWidth, height: innerHeight},
358
+ reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
359
+ horizontalOverflow:
360
+ document.documentElement.scrollWidth > innerWidth + 1,
361
+ fonts: document.fonts.status,
362
+ surfaceRole:
363
+ element.getAttribute('role') ??
364
+ (element instanceof HTMLDialogElement ? 'dialog' : null),
365
+ surfaceName:
366
+ element.getAttribute('aria-label') ??
367
+ element.getAttribute('aria-labelledby'),
368
+ geometry: {x: box.x, y: box.y, width: box.width, height: box.height},
369
+ };
370
+ });
371
+ const geometryPass =
372
+ observed.geometry !== null &&
373
+ observed.geometry.width > 0 &&
374
+ observed.geometry.height > 0;
375
+ let pointerPlacementPass = true;
376
+ if (item.storyId === 'core-contextmenu--pointer-presentation') {
377
+ const surfaceBox = await surface.boundingBox();
378
+ pointerPlacementPass =
379
+ surfaceBox !== null &&
380
+ Math.abs(
381
+ (item.direction === 'rtl'
382
+ ? surfaceBox.x + surfaceBox.width
383
+ : surfaceBox.x) - pointer.x,
384
+ ) <= 24 &&
385
+ Math.abs(surfaceBox.y - pointer.y) <= 24;
386
+ }
387
+
388
+ const png = await page.screenshot({animations: 'disabled', fullPage: true});
389
+ const image = `${item.key}.png`;
390
+ const receipt = {
391
+ schemaVersion: 1,
392
+ expected: {
393
+ build: checkoutSha,
394
+ storyId: item.storyId,
395
+ mode: item.mode,
396
+ direction: item.direction,
397
+ theme: item.theme,
398
+ viewport: item.viewport,
399
+ surface: item.surface,
400
+ state: item.expectedState ?? 'default',
401
+ },
402
+ observed: {
403
+ build: storybookSha,
404
+ ...observed,
405
+ menuItemCount: await menuItems.count(),
406
+ pageErrors,
407
+ },
408
+ image: {
409
+ file: image,
410
+ sha256: createHash('sha256').update(png).digest('hex'),
411
+ width: png.readUInt32BE(16),
412
+ height: png.readUInt32BE(20),
413
+ },
414
+ checks: {geometryPass, pointerPlacementPass},
415
+ passed:
416
+ storybookSha === checkoutSha &&
417
+ pageErrors.length === 0 &&
418
+ observed.theme === item.theme &&
419
+ observed.mode === item.mode &&
420
+ observed.direction === item.direction &&
421
+ observed.viewport.width === item.viewport.width &&
422
+ observed.viewport.height === item.viewport.height &&
423
+ observed.reducedMotion &&
424
+ !observed.horizontalOverflow &&
425
+ observed.fonts === 'loaded' &&
426
+ observed.surfaceRole === item.surface &&
427
+ geometryPass &&
428
+ pointerPlacementPass,
429
+ };
430
+ fs.writeFileSync(path.join(OUTPUT, image), png);
431
+ fs.writeFileSync(
432
+ path.join(OUTPUT, `${item.key}.sensors.json`),
433
+ `${JSON.stringify(receipt, null, 2)}\n`,
434
+ );
435
+ expect(receipt.passed).toBe(true);
436
+ await context.close();
437
+ });
438
+ }
@@ -13,7 +13,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
13
  import {readFileSync} from 'node:fs';
14
14
  import {render, screen, fireEvent, waitFor, act} from '@testing-library/react';
15
15
  import userEvent from '@testing-library/user-event';
16
- import {useState} from 'react';
16
+ import {createRef, useState} from 'react';
17
17
  import * as stylex from '@stylexjs/stylex';
18
18
  import {DropdownMenu, type DropdownMenuTriggerProps} from './DropdownMenu';
19
19
  import {DropdownMenuItem} from './DropdownMenuItem';
@@ -24,6 +24,7 @@ import {Divider} from '../Divider';
24
24
  import {rtlStyles} from '../utils';
25
25
  import {__resetInteractionModalityForTest} from '../utils/interactionModality';
26
26
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
27
+ import {spacingVars} from '../theme/tokens.stylex';
27
28
 
28
29
  // Mock showPopover and hidePopover methods since they're not implemented in jsdom
29
30
  beforeEach(() => {
@@ -2529,6 +2530,45 @@ describe('DropdownMenu data/compound parity', () => {
2529
2530
  expect(separator).toHaveClass('astryx-dropdown-menu-divider');
2530
2531
  });
2531
2532
 
2533
+ it('composes the complete public root surface on the separator', () => {
2534
+ const ref = createRef<HTMLDivElement>();
2535
+ const onClick = vi.fn();
2536
+ const customStyles = stylex.create({
2537
+ divider: {marginBlock: spacingVars['--spacing-5']},
2538
+ });
2539
+ const customClass = stylex
2540
+ .props(customStyles.divider)
2541
+ .className!.split(' ')
2542
+ .pop()!;
2543
+
2544
+ render(
2545
+ <DropdownMenu button={{label: 'Actions'}}>
2546
+ <DropdownMenuItem label="Edit" />
2547
+ <DropdownMenuDivider
2548
+ ref={ref}
2549
+ data-testid="menu-separator"
2550
+ className="consumer-divider"
2551
+ style={{backgroundColor: 'rgb(1, 2, 3)'}}
2552
+ xstyle={customStyles.divider}
2553
+ onClick={onClick}
2554
+ />
2555
+ <DropdownMenuItem label="Delete" />
2556
+ </DropdownMenu>,
2557
+ );
2558
+
2559
+ const separator = screen.getByTestId('menu-separator');
2560
+ expect(ref.current).toBe(separator);
2561
+ expect(separator).toHaveClass(
2562
+ 'astryx-dropdown-menu-divider',
2563
+ 'astryx-divider',
2564
+ 'consumer-divider',
2565
+ customClass,
2566
+ );
2567
+ expect(separator).toHaveStyle({backgroundColor: 'rgb(1, 2, 3)'});
2568
+ fireEvent.click(separator);
2569
+ expect(onClick).toHaveBeenCalledTimes(1);
2570
+ });
2571
+
2532
2572
  it('skips a compound divider in the arrow-key order', async () => {
2533
2573
  const user = userEvent.setup();
2534
2574
  render(