@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.
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +37 -5
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +4 -2
- package/dist/MobileNav/MobileNav.d.ts +9 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +30 -3
- package/dist/SideNav/SideNavItem.d.ts +8 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +71 -9
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +3 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +3 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +2 -2
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -9
- package/dist/astryx.css +11 -1
- package/package.json +3 -3
- package/src/AppShell/AppShell.tsx +45 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Card/Card.doc.mjs +1 -1
- package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
- package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
- package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
- package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +41 -1
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/MobileNav/MobileNav.tsx +30 -3
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +173 -15
- package/src/SideNav/SideNav.test.tsx +179 -0
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +96 -17
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
- package/src/TopNav/TopNavMegaMenu.tsx +7 -3
- package/src/TopNav/TopNavMenu.test.tsx +19 -0
- package/src/TopNav/TopNavMenu.tsx +11 -3
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/TreeList/TreeList.test.tsx +327 -6
- package/src/TreeList/TreeList.tsx +5 -1
- package/src/TreeList/TreeListBranches.tsx +6 -0
- package/src/TreeList/TreeListItem.tsx +88 -13
- 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(
|