@astryxdesign/core 0.7.1 → 0.7.2-canary.01eab6a
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/AspectRatio/AspectRatio.d.ts +6 -8
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +44 -3
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +38 -14
- package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +3 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -2
- 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/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +20 -8
- package/dist/Drawer/Drawer.d.ts +191 -0
- package/dist/Drawer/Drawer.d.ts.map +1 -0
- package/dist/Drawer/Drawer.js +382 -0
- package/dist/Drawer/DrawerHeader.d.ts +85 -0
- package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
- package/dist/Drawer/DrawerHeader.js +134 -0
- package/dist/Drawer/index.d.ts +11 -0
- package/dist/Drawer/index.d.ts.map +1 -0
- package/dist/Drawer/index.js +11 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
- package/dist/Drawer/useDrawerDialogPresence.js +236 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +47 -4
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +4 -10
- package/dist/Field/Field.d.ts +1 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +8 -4
- package/dist/Grid/GridSpan.d.ts.map +1 -1
- package/dist/Grid/GridSpan.js +1 -0
- package/dist/Heading/Heading.d.ts.map +1 -1
- package/dist/Heading/Heading.js +4 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -2
- package/dist/Item/Item.d.ts +6 -2
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +19 -4
- package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
- package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
- package/dist/Layer/layerViewportInset.stylex.js +6 -2
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +13 -7
- package/dist/Layer/useLayerDismissal.d.ts +1 -1
- package/dist/Layer/useLayerDismissal.js +1 -1
- package/dist/Layout/container.stylex.d.ts +5 -5
- package/dist/Layout/container.stylex.js +4 -4
- package/dist/List/List.d.ts +46 -19
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +28 -10
- package/dist/List/ListContext.d.ts +8 -3
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +7 -2
- package/dist/List/ListItem.d.ts +13 -5
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +21 -8
- package/dist/List/index.d.ts +1 -1
- package/dist/List/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +8 -1
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
- package/dist/Markdown/plugins/frontmatter.js +7 -3
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +5 -1
- package/dist/MetadataList/MetadataListContext.d.ts +1 -1
- package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +22 -8
- package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +3 -1
- package/dist/Pagination/Pagination.d.ts +8 -5
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +21 -8
- package/dist/Popover/Popover.js +2 -2
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/RadioList/RadioList.d.ts +1 -1
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +6 -2
- package/dist/RadioList/RadioListItem.d.ts +9 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +2 -0
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +6 -2
- 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/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +13 -1
- package/dist/TabList/TabMenu.d.ts +2 -2
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +6 -3
- package/dist/Text/Text.d.ts.map +1 -1
- package/dist/Text/Text.js +4 -2
- package/dist/Text/useTruncation.d.ts +7 -5
- package/dist/Text/useTruncation.d.ts.map +1 -1
- package/dist/Text/useTruncation.js +16 -5
- package/dist/ToggleButton/ToggleButton.d.ts +3 -2
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +12 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -0
- 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/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +26 -0
- package/dist/astryx.css +47 -13
- package/dist/hooks/useEntryAnimation.d.ts +2 -1
- package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
- package/dist/hooks/useEntryAnimation.js +14 -12
- package/dist/hooks/useIsHydrating.d.ts +6 -0
- package/dist/hooks/useIsHydrating.d.ts.map +1 -0
- package/dist/hooks/useIsHydrating.js +30 -0
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +18 -13
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +112 -1
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +0 -3
- package/dist/utils/focusOutline.stylex.d.ts +4 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
- package/dist/utils/focusOutline.stylex.js +4 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +15 -9
- package/dist/utils/sharedMutationObserver.d.ts +21 -0
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
- package/dist/utils/sharedMutationObserver.js +69 -0
- package/locales/en.json +444 -0
- package/locales/pseudo.json +333 -0
- package/package.json +9 -4
- package/src/AspectRatio/AspectRatio.test.tsx +44 -43
- package/src/AspectRatio/AspectRatio.tsx +38 -18
- package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
- package/src/Avatar/Avatar.doc.mjs +5 -3
- package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.test.tsx +155 -9
- package/src/Button/Button.tsx +39 -16
- package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
- package/src/Button/__tests__/Button.a11y.states.ts +0 -10
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +3 -1
- package/src/CheckboxList/CheckboxList.test.tsx +22 -0
- package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
- package/src/CheckboxList/CheckboxListItem.tsx +10 -0
- 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/ContextMenu.tsx +4 -1
- package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
- package/src/Dialog/Dialog.test.tsx +46 -0
- package/src/Dialog/Dialog.tsx +20 -7
- package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
- package/src/Drawer/Drawer.doc.mjs +472 -0
- package/src/Drawer/Drawer.spec.md +357 -0
- package/src/Drawer/Drawer.test.tsx +1300 -0
- package/src/Drawer/Drawer.tsx +613 -0
- package/src/Drawer/DrawerDirection.test.tsx +127 -0
- package/src/Drawer/DrawerHeader.doc.mjs +293 -0
- package/src/Drawer/DrawerHeader.test.tsx +102 -0
- package/src/Drawer/DrawerHeader.tsx +235 -0
- package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
- package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
- package/src/Drawer/index.ts +13 -0
- package/src/Drawer/useDrawerDialogPresence.ts +284 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +62 -13
- package/src/DropdownMenu/DropdownMenu.tsx +47 -4
- package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
- package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
- package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
- package/src/EmptyState/EmptyState.doc.mjs +3 -2
- package/src/EmptyState/EmptyState.spec.md +189 -0
- package/src/EmptyState/EmptyState.test.tsx +74 -3
- package/src/EmptyState/EmptyState.tsx +4 -8
- package/src/Field/Field.doc.mjs +14 -8
- package/src/Field/Field.spec.md +217 -0
- package/src/Field/Field.test.tsx +60 -2
- package/src/Field/Field.tsx +17 -8
- package/src/Grid/GridSpan.tsx +3 -0
- package/src/Heading/Heading.test.tsx +57 -0
- package/src/Heading/Heading.tsx +6 -2
- package/src/HoverCard/HoverCard.test.tsx +58 -0
- package/src/HoverCard/useHoverCard.tsx +10 -2
- package/src/IconButton/IconButton.doc.mjs +2 -2
- package/src/IconButton/IconButton.test.tsx +3 -1
- package/src/Item/Item.doc.mjs +12 -8
- package/src/Item/Item.spec.md +226 -0
- package/src/Item/Item.test.tsx +61 -0
- package/src/Item/Item.tsx +40 -4
- package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
- package/src/Layer/layerViewportInset.stylex.ts +6 -2
- package/src/Layer/layerViewportInset.test.ts +1 -0
- package/src/Layer/useLayer.tsx +24 -13
- package/src/Layer/useLayerDismissal.ts +1 -1
- package/src/Layout/container.stylex.ts +6 -6
- package/src/List/List.doc.mjs +14 -1
- package/src/List/List.spec.md +129 -77
- package/src/List/List.test.tsx +163 -94
- package/src/List/List.tsx +74 -18
- package/src/List/ListContext.tsx +9 -3
- package/src/List/ListItem.doc.mjs +24 -5
- package/src/List/ListItem.tsx +56 -27
- package/src/List/index.ts +1 -1
- package/src/Markdown/Markdown.tsx +14 -1
- package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
- package/src/Markdown/plugins/frontmatter.ts +7 -3
- package/src/MetadataList/MetadataList.doc.mjs +2 -2
- package/src/MetadataList/MetadataList.test.tsx +38 -0
- package/src/MetadataList/MetadataList.tsx +8 -1
- package/src/MetadataList/MetadataListContext.tsx +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +159 -0
- package/src/MultiSelector/MultiSelector.tsx +40 -7
- package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
- package/src/MultiSelector/hooks.ts +3 -1
- package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
- package/src/Pagination/Pagination.doc.mjs +7 -7
- package/src/Pagination/Pagination.spec.md +9 -9
- package/src/Pagination/Pagination.test.tsx +242 -0
- package/src/Pagination/Pagination.tsx +27 -11
- package/src/Popover/Popover.test.tsx +2 -2
- package/src/Popover/Popover.tsx +4 -4
- package/src/PowerSearch/PowerSearch.test.tsx +85 -0
- package/src/PowerSearch/PowerSearch.tsx +2 -2
- package/src/RadioList/RadioList.fr10.test.tsx +45 -2
- package/src/RadioList/RadioList.test.tsx +22 -0
- package/src/RadioList/RadioList.tsx +4 -1
- package/src/RadioList/RadioListItem.doc.mjs +16 -0
- package/src/RadioList/RadioListItem.tsx +10 -1
- package/src/Selector/hooks.ts +6 -2
- package/src/Selector/useCombobox.doc.mjs +237 -0
- 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/Slider/Slider.spec.md +39 -18
- package/src/Switch/Switch.test.tsx +49 -0
- package/src/Switch/Switch.tsx +11 -0
- package/src/TabList/TabList.spec.md +57 -44
- package/src/TabList/TabList.test.tsx +67 -0
- package/src/TabList/TabMenu.doc.mjs +4 -3
- package/src/TabList/TabMenu.tsx +7 -4
- package/src/Text/Text.test.tsx +57 -0
- package/src/Text/Text.tsx +6 -2
- package/src/Text/useTruncation.test.tsx +69 -0
- package/src/Text/useTruncation.ts +16 -5
- package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
- package/src/ToggleButton/ToggleButton.test.tsx +95 -0
- package/src/ToggleButton/ToggleButton.tsx +12 -1
- package/src/Tokenizer/Tokenizer.test.tsx +54 -1
- package/src/Tooltip/Tooltip.test.tsx +31 -0
- package/src/Tooltip/useTooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +11 -0
- 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.spec.md +29 -17
- 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/Typeahead/BaseTypeahead.tsx +26 -0
- package/src/Typeahead/Typeahead.test.tsx +260 -2
- package/src/docPropLiterals.test.ts +36 -10
- package/src/hooks/useEntryAnimation.doc.mjs +4 -4
- package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
- package/src/hooks/useEntryAnimation.ts +15 -16
- package/src/hooks/useIsHydrating.test.tsx +70 -0
- package/src/hooks/useIsHydrating.ts +31 -0
- package/src/hooks/useMenuHover.ts +18 -13
- package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
- package/src/i18n/InternationalizationProvider.spec.md +252 -0
- package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
- package/src/i18n/generated-locales/en.generated.ts +121 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
- package/src/i18n/useTranslator.test.tsx +37 -0
- package/src/index.ts +1 -0
- package/src/reset.css +0 -29
- package/src/theme/derivedVarRegistry.test.ts +7 -3
- package/src/theme/derivedVarRegistry.ts +0 -3
- package/src/utils/focusOutline.stylex.ts +4 -0
- package/src/utils/plainDate.test.ts +37 -1
- package/src/utils/plainDate.ts +15 -5
- package/src/utils/sharedMutationObserver.test.ts +53 -0
- package/src/utils/sharedMutationObserver.ts +78 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandPaletteInputAudit.a11y.chromium.spec.ts
|
|
5
|
+
* @input Exact-head CommandPaletteInput stories across semantic, theme, direction, viewport, and focus states
|
|
6
|
+
* @output Screenshots and fail-closed sensor receipts for the CommandPaletteInput whole-component audit
|
|
7
|
+
* @position Component-owned Night Watch browser evidence; CI runs it only for a CommandPaletteInput 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('command-palette-input-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 Story = 'standalone' | 'popup-context' | 'aggregate-matrix';
|
|
30
|
+
type State = 'rest' | 'keyboard-focus' | 'pointer-focus' | 'context-matrix';
|
|
31
|
+
|
|
32
|
+
type AuditReceipt = {
|
|
33
|
+
passed: boolean;
|
|
34
|
+
expected: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type AuditCase = {
|
|
38
|
+
key: string;
|
|
39
|
+
story: Story;
|
|
40
|
+
mode: Mode;
|
|
41
|
+
direction: Direction;
|
|
42
|
+
theme: Theme;
|
|
43
|
+
state: State;
|
|
44
|
+
viewport: {width: number; height: number};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const cases: AuditCase[] = [];
|
|
48
|
+
function addPair(
|
|
49
|
+
key: string,
|
|
50
|
+
options: Partial<Omit<AuditCase, 'key' | 'mode'>> = {},
|
|
51
|
+
) {
|
|
52
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
53
|
+
cases.push({
|
|
54
|
+
key: `${key}-${mode}`,
|
|
55
|
+
story: options.story ?? 'standalone',
|
|
56
|
+
mode,
|
|
57
|
+
direction: options.direction ?? 'ltr',
|
|
58
|
+
theme: options.theme ?? 'neutral',
|
|
59
|
+
state: options.state ?? 'rest',
|
|
60
|
+
viewport: options.viewport ?? WIDE,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addPair('standalone-rest');
|
|
66
|
+
addPair('standalone-keyboard-focus', {state: 'keyboard-focus'});
|
|
67
|
+
addPair('standalone-pointer-focus', {state: 'pointer-focus'});
|
|
68
|
+
addPair('standalone-keyboard-focus-custom-theme', {
|
|
69
|
+
state: 'keyboard-focus',
|
|
70
|
+
theme: 'butter',
|
|
71
|
+
});
|
|
72
|
+
addPair('standalone-keyboard-focus-rtl', {
|
|
73
|
+
state: 'keyboard-focus',
|
|
74
|
+
direction: 'rtl',
|
|
75
|
+
});
|
|
76
|
+
addPair('standalone-keyboard-focus-narrow', {
|
|
77
|
+
state: 'keyboard-focus',
|
|
78
|
+
viewport: NARROW,
|
|
79
|
+
});
|
|
80
|
+
addPair('popup-rest', {story: 'popup-context'});
|
|
81
|
+
addPair('popup-keyboard-focus', {
|
|
82
|
+
story: 'popup-context',
|
|
83
|
+
state: 'keyboard-focus',
|
|
84
|
+
});
|
|
85
|
+
addPair('popup-pointer-focus', {
|
|
86
|
+
story: 'popup-context',
|
|
87
|
+
state: 'pointer-focus',
|
|
88
|
+
});
|
|
89
|
+
addPair('context-matrix', {
|
|
90
|
+
story: 'aggregate-matrix',
|
|
91
|
+
state: 'context-matrix',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test.describe.configure({mode: 'serial', retries: 0});
|
|
95
|
+
let server: StaticServer;
|
|
96
|
+
let checkoutSha: string;
|
|
97
|
+
let storybookSha: string;
|
|
98
|
+
|
|
99
|
+
function storyId(item: AuditCase) {
|
|
100
|
+
return item.story === 'aggregate-matrix'
|
|
101
|
+
? 'core-commandpalette--audit-matrix'
|
|
102
|
+
: `core-commandpaletteinput--${item.story}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function storyUrl(item: AuditCase) {
|
|
106
|
+
return `${server.origin}/iframe.html?id=${storyId(item)}&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function settle(page: Page) {
|
|
110
|
+
await holdMotionStill(page);
|
|
111
|
+
await page.evaluate(async () => document.fonts.ready);
|
|
112
|
+
await page.waitForTimeout(40);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function focusFromKeyboard(page: Page, input: Locator) {
|
|
116
|
+
await page.keyboard.press('Tab');
|
|
117
|
+
if (!(await input.evaluate(element => element === document.activeElement))) {
|
|
118
|
+
await input.focus();
|
|
119
|
+
}
|
|
120
|
+
await expect(input).toBeFocused();
|
|
121
|
+
expect(
|
|
122
|
+
await input.evaluate(element => element.matches(':focus-visible')),
|
|
123
|
+
).toBe(true);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function focusFromPointer(input: Locator) {
|
|
127
|
+
// PopupContext is already focused by Dialog's open-time focus management.
|
|
128
|
+
// Move focus away first so the click acquires focus and runs the component's
|
|
129
|
+
// pointer-modality gate rather than merely clicking an existing keyboard focus.
|
|
130
|
+
await input.blur();
|
|
131
|
+
await expect(input).not.toBeFocused();
|
|
132
|
+
await input.click();
|
|
133
|
+
await expect(input).toBeFocused();
|
|
134
|
+
// CSS Selectors 4 deliberately keeps text-entry controls focus-visible after
|
|
135
|
+
// a pointer click. The component's shared modality gate must still withhold
|
|
136
|
+
// its keyboard-only ring.
|
|
137
|
+
expect(
|
|
138
|
+
await input.evaluate(element => element.matches(':focus-visible')),
|
|
139
|
+
).toBe(true);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test.beforeAll(async () => {
|
|
143
|
+
fs.rmSync(OUTPUT, {recursive: true, force: true});
|
|
144
|
+
fs.mkdirSync(OUTPUT, {recursive: true});
|
|
145
|
+
checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
146
|
+
encoding: 'utf8',
|
|
147
|
+
}).trim();
|
|
148
|
+
const expected = process.env.ASTRYX_HEAD_SHA;
|
|
149
|
+
if (expected && expected !== checkoutSha) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`checkout ${checkoutSha} does not match expected head ${expected}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
server = await serveStorybook(
|
|
155
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
156
|
+
);
|
|
157
|
+
const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw new Error(`Storybook build has no SHA stamp (${response.status})`);
|
|
160
|
+
}
|
|
161
|
+
storybookSha = (await response.text()).trim();
|
|
162
|
+
if (storybookSha !== checkoutSha) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test.afterAll(async () => {
|
|
170
|
+
await server?.close();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
for (const item of cases) {
|
|
174
|
+
test(item.key, async ({browser}) => {
|
|
175
|
+
const context = await browser.newContext({
|
|
176
|
+
viewport: item.viewport,
|
|
177
|
+
colorScheme: item.mode,
|
|
178
|
+
reducedMotion: 'reduce',
|
|
179
|
+
});
|
|
180
|
+
const page = await context.newPage();
|
|
181
|
+
const pageErrors: string[] = [];
|
|
182
|
+
page.on('pageerror', error => pageErrors.push(String(error)));
|
|
183
|
+
await page.goto(storyUrl(item), {waitUntil: 'load'});
|
|
184
|
+
await page.locator('#storybook-root').waitFor();
|
|
185
|
+
|
|
186
|
+
let input: Locator;
|
|
187
|
+
if (item.story === 'aggregate-matrix') {
|
|
188
|
+
const matrix = page.locator('[data-command-palette-audit-matrix]');
|
|
189
|
+
await expect(matrix).toBeVisible();
|
|
190
|
+
await expect(matrix.getByRole('combobox')).toHaveCount(3);
|
|
191
|
+
await expect(matrix.locator('.astryx-spinner')).toHaveCount(1);
|
|
192
|
+
await expect(matrix.getByRole('option')).not.toHaveCount(0);
|
|
193
|
+
input = matrix
|
|
194
|
+
.getByRole('region', {name: 'Grouped command palette'})
|
|
195
|
+
.getByRole('combobox');
|
|
196
|
+
} else if (item.story === 'popup-context') {
|
|
197
|
+
input = page.getByRole('combobox', {name: 'Search commands'});
|
|
198
|
+
await expect(input).toHaveAttribute('aria-expanded', 'true');
|
|
199
|
+
await expect(input).toHaveAttribute('aria-controls', /.+/);
|
|
200
|
+
} else {
|
|
201
|
+
input = page.getByRole('searchbox', {name: 'Search documentation'});
|
|
202
|
+
await expect(input).not.toHaveAttribute('aria-expanded');
|
|
203
|
+
await expect(input).not.toHaveAttribute('aria-controls');
|
|
204
|
+
await expect(page.getByTestId('input-end-content')).toBeVisible();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await expect(input).toBeVisible();
|
|
208
|
+
if (item.state === 'keyboard-focus') {
|
|
209
|
+
await focusFromKeyboard(page, input);
|
|
210
|
+
} else if (item.state === 'pointer-focus') {
|
|
211
|
+
await focusFromPointer(input);
|
|
212
|
+
}
|
|
213
|
+
await settle(page);
|
|
214
|
+
|
|
215
|
+
const inputBox = await input.boundingBox();
|
|
216
|
+
if (inputBox == null) {
|
|
217
|
+
throw new Error(`${item.key}: the visible input has no geometry`);
|
|
218
|
+
}
|
|
219
|
+
const observed = await page.evaluate(() => ({
|
|
220
|
+
theme: document
|
|
221
|
+
.querySelector('[data-astryx-theme]')
|
|
222
|
+
?.getAttribute('data-astryx-theme'),
|
|
223
|
+
mode: document.documentElement.getAttribute('data-theme'),
|
|
224
|
+
direction: getComputedStyle(
|
|
225
|
+
document.querySelector('.astryx-command-palette-input') as HTMLElement,
|
|
226
|
+
).direction,
|
|
227
|
+
viewport: {width: innerWidth, height: innerHeight},
|
|
228
|
+
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
229
|
+
horizontalOverflow: document.documentElement.scrollWidth > innerWidth + 1,
|
|
230
|
+
fonts: document.fonts.status,
|
|
231
|
+
}));
|
|
232
|
+
const focus = await input.evaluate(element => {
|
|
233
|
+
const style = getComputedStyle(element);
|
|
234
|
+
return {
|
|
235
|
+
focused: element === document.activeElement,
|
|
236
|
+
focusVisible: element.matches(':focus-visible'),
|
|
237
|
+
outlineWidth: style.outlineWidth,
|
|
238
|
+
outlineStyle: style.outlineStyle,
|
|
239
|
+
outlineColor: style.outlineColor,
|
|
240
|
+
outlineOffset: style.outlineOffset,
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
const ringVisible =
|
|
244
|
+
focus.outlineStyle !== 'none' &&
|
|
245
|
+
Number.parseFloat(focus.outlineWidth) >= 2;
|
|
246
|
+
const focusIndicatorPass =
|
|
247
|
+
item.state === 'keyboard-focus'
|
|
248
|
+
? focus.focused && focus.focusVisible && ringVisible
|
|
249
|
+
: item.state === 'pointer-focus'
|
|
250
|
+
? focus.focused && focus.focusVisible && !ringVisible
|
|
251
|
+
: true;
|
|
252
|
+
|
|
253
|
+
const png = await page.screenshot({animations: 'disabled', fullPage: true});
|
|
254
|
+
const image = `${item.key}.png`;
|
|
255
|
+
const receipt = {
|
|
256
|
+
schemaVersion: 1,
|
|
257
|
+
expected: {
|
|
258
|
+
build: checkoutSha,
|
|
259
|
+
storyId: storyId(item),
|
|
260
|
+
mode: item.mode,
|
|
261
|
+
direction: item.direction,
|
|
262
|
+
theme: item.theme,
|
|
263
|
+
state: item.state,
|
|
264
|
+
viewport: item.viewport,
|
|
265
|
+
},
|
|
266
|
+
observed: {
|
|
267
|
+
build: storybookSha,
|
|
268
|
+
...observed,
|
|
269
|
+
role: await input.getAttribute('role'),
|
|
270
|
+
name: await input.getAttribute('aria-label'),
|
|
271
|
+
geometry: inputBox,
|
|
272
|
+
focus,
|
|
273
|
+
pageErrors,
|
|
274
|
+
},
|
|
275
|
+
image: {
|
|
276
|
+
file: image,
|
|
277
|
+
sha256: createHash('sha256').update(png).digest('hex'),
|
|
278
|
+
width: png.readUInt32BE(16),
|
|
279
|
+
height: png.readUInt32BE(20),
|
|
280
|
+
},
|
|
281
|
+
checks: {focusIndicatorPass},
|
|
282
|
+
passed:
|
|
283
|
+
storybookSha === checkoutSha &&
|
|
284
|
+
pageErrors.length === 0 &&
|
|
285
|
+
observed.theme === item.theme &&
|
|
286
|
+
observed.mode === item.mode &&
|
|
287
|
+
observed.direction === item.direction &&
|
|
288
|
+
observed.viewport.width === item.viewport.width &&
|
|
289
|
+
observed.viewport.height === item.viewport.height &&
|
|
290
|
+
observed.reducedMotion &&
|
|
291
|
+
!observed.horizontalOverflow &&
|
|
292
|
+
observed.fonts === 'loaded' &&
|
|
293
|
+
focusIndicatorPass,
|
|
294
|
+
};
|
|
295
|
+
fs.writeFileSync(path.join(OUTPUT, image), png);
|
|
296
|
+
fs.writeFileSync(
|
|
297
|
+
path.join(OUTPUT, `${item.key}.sensors.json`),
|
|
298
|
+
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
await context.close();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
test('manifest is complete', async () => {
|
|
306
|
+
const receiptFiles = fs
|
|
307
|
+
.readdirSync(OUTPUT)
|
|
308
|
+
.filter(file => file.endsWith('.sensors.json'));
|
|
309
|
+
const receipts = receiptFiles.map(
|
|
310
|
+
(file): AuditReceipt =>
|
|
311
|
+
JSON.parse(
|
|
312
|
+
fs.readFileSync(path.join(OUTPUT, file), 'utf8'),
|
|
313
|
+
) as AuditReceipt,
|
|
314
|
+
);
|
|
315
|
+
const manifest = {
|
|
316
|
+
schemaVersion: 1,
|
|
317
|
+
component: 'core/CommandPaletteInput',
|
|
318
|
+
build: checkoutSha,
|
|
319
|
+
frames: cases.map(item => ({
|
|
320
|
+
key: item.key,
|
|
321
|
+
image: `${item.key}.png`,
|
|
322
|
+
receipt: `${item.key}.sensors.json`,
|
|
323
|
+
})),
|
|
324
|
+
};
|
|
325
|
+
fs.writeFileSync(
|
|
326
|
+
path.join(OUTPUT, 'manifest.json'),
|
|
327
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
328
|
+
);
|
|
329
|
+
expect(receiptFiles).toHaveLength(cases.length);
|
|
330
|
+
expect(
|
|
331
|
+
receipts
|
|
332
|
+
.filter(receipt => !receipt.passed)
|
|
333
|
+
.map(receipt => receipt.expected),
|
|
334
|
+
).toEqual([]);
|
|
335
|
+
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandPaletteItemAudit.a11y.chromium.spec.ts
|
|
5
|
+
* @input Exact-head CommandPaletteItem stories across semantics, states, themes, direction, and viewport sizes
|
|
6
|
+
* @output Screenshots and fail-closed sensor receipts for the CommandPaletteItem whole-component audit
|
|
7
|
+
* @position Component-owned Night Watch browser evidence; CI runs it only for a CommandPaletteItem 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 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('command-palette-item-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 Story = 'states' | 'combined-and-long';
|
|
30
|
+
type State = 'rest' | 'hover';
|
|
31
|
+
|
|
32
|
+
type AuditCase = {
|
|
33
|
+
key: string;
|
|
34
|
+
story: Story;
|
|
35
|
+
mode: Mode;
|
|
36
|
+
direction: Direction;
|
|
37
|
+
theme: Theme;
|
|
38
|
+
state: State;
|
|
39
|
+
viewport: {width: number; height: number};
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type AuditReceipt = {
|
|
43
|
+
passed: boolean;
|
|
44
|
+
expected: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const cases: AuditCase[] = [];
|
|
48
|
+
function addPair(
|
|
49
|
+
key: string,
|
|
50
|
+
options: Partial<Omit<AuditCase, 'key' | 'mode'>> = {},
|
|
51
|
+
) {
|
|
52
|
+
for (const mode of ['light', 'dark'] as const) {
|
|
53
|
+
cases.push({
|
|
54
|
+
key: `${key}-${mode}`,
|
|
55
|
+
story: options.story ?? 'states',
|
|
56
|
+
mode,
|
|
57
|
+
direction: options.direction ?? 'ltr',
|
|
58
|
+
theme: options.theme ?? 'neutral',
|
|
59
|
+
state: options.state ?? 'rest',
|
|
60
|
+
viewport: options.viewport ?? WIDE,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addPair('states-rest');
|
|
66
|
+
addPair('states-hover', {state: 'hover'});
|
|
67
|
+
addPair('states-custom-theme', {theme: 'butter'});
|
|
68
|
+
addPair('states-rtl', {direction: 'rtl'});
|
|
69
|
+
addPair('states-narrow', {viewport: NARROW});
|
|
70
|
+
addPair('combined-rest', {story: 'combined-and-long'});
|
|
71
|
+
addPair('combined-custom-theme', {
|
|
72
|
+
story: 'combined-and-long',
|
|
73
|
+
theme: 'butter',
|
|
74
|
+
});
|
|
75
|
+
addPair('combined-narrow', {
|
|
76
|
+
story: 'combined-and-long',
|
|
77
|
+
viewport: NARROW,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test.describe.configure({mode: 'serial', retries: 0});
|
|
81
|
+
let server: StaticServer;
|
|
82
|
+
let checkoutSha: string;
|
|
83
|
+
let storybookSha: string;
|
|
84
|
+
|
|
85
|
+
function storyId(item: AuditCase) {
|
|
86
|
+
return `core-commandpaletteitem--${item.story}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function storyUrl(item: AuditCase) {
|
|
90
|
+
return `${server.origin}/iframe.html?id=${storyId(item)}&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function settle(page: Page) {
|
|
94
|
+
await holdMotionStill(page);
|
|
95
|
+
await page.evaluate(async () => document.fonts.ready);
|
|
96
|
+
await page.waitForTimeout(40);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
test.beforeAll(async () => {
|
|
100
|
+
fs.rmSync(OUTPUT, {recursive: true, force: true});
|
|
101
|
+
fs.mkdirSync(OUTPUT, {recursive: true});
|
|
102
|
+
checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
}).trim();
|
|
105
|
+
const expected = process.env.ASTRYX_HEAD_SHA;
|
|
106
|
+
if (expected && expected !== checkoutSha) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`checkout ${checkoutSha} does not match expected head ${expected}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
server = await serveStorybook(
|
|
112
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
113
|
+
);
|
|
114
|
+
const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`Storybook build has no SHA stamp (${response.status})`);
|
|
117
|
+
}
|
|
118
|
+
storybookSha = (await response.text()).trim();
|
|
119
|
+
if (storybookSha !== checkoutSha) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test.afterAll(async () => {
|
|
127
|
+
await server?.close();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
for (const item of cases) {
|
|
131
|
+
test(item.key, async ({browser}) => {
|
|
132
|
+
const context = await browser.newContext({
|
|
133
|
+
viewport: item.viewport,
|
|
134
|
+
colorScheme: item.mode,
|
|
135
|
+
reducedMotion: 'reduce',
|
|
136
|
+
});
|
|
137
|
+
const page = await context.newPage();
|
|
138
|
+
const pageErrors: string[] = [];
|
|
139
|
+
page.on('pageerror', error => pageErrors.push(String(error)));
|
|
140
|
+
await page.goto(storyUrl(item), {waitUntil: 'load'});
|
|
141
|
+
await page.locator('#storybook-root').waitFor();
|
|
142
|
+
|
|
143
|
+
const listName =
|
|
144
|
+
item.story === 'states'
|
|
145
|
+
? 'Command item states'
|
|
146
|
+
: 'Combined and long command item states';
|
|
147
|
+
const list = page.getByRole('listbox', {name: listName});
|
|
148
|
+
const options = list.getByRole('option');
|
|
149
|
+
const expectedCount = item.story === 'states' ? 4 : 2;
|
|
150
|
+
const expectedDisabled = item.story === 'states' ? 1 : 0;
|
|
151
|
+
await expect(list).toBeVisible();
|
|
152
|
+
await expect(options).toHaveCount(expectedCount);
|
|
153
|
+
await expect(
|
|
154
|
+
list.locator('[role="option"][aria-selected="true"]'),
|
|
155
|
+
).toHaveCount(1);
|
|
156
|
+
await expect(
|
|
157
|
+
list.locator('[role="option"][aria-disabled="true"]'),
|
|
158
|
+
).toHaveCount(expectedDisabled);
|
|
159
|
+
await expect(list.locator('button, a, [tabindex]')).toHaveCount(0);
|
|
160
|
+
|
|
161
|
+
if (item.state === 'hover') {
|
|
162
|
+
await options.first().hover();
|
|
163
|
+
}
|
|
164
|
+
await settle(page);
|
|
165
|
+
|
|
166
|
+
const observed = await page.evaluate(() => ({
|
|
167
|
+
theme: document
|
|
168
|
+
.querySelector('[data-astryx-theme]')
|
|
169
|
+
?.getAttribute('data-astryx-theme'),
|
|
170
|
+
mode: document.documentElement.getAttribute('data-theme'),
|
|
171
|
+
direction: getComputedStyle(
|
|
172
|
+
document.querySelector('.astryx-command-palette-item') as HTMLElement,
|
|
173
|
+
).direction,
|
|
174
|
+
viewport: {width: innerWidth, height: innerHeight},
|
|
175
|
+
reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
|
|
176
|
+
horizontalOverflow: document.documentElement.scrollWidth > innerWidth + 1,
|
|
177
|
+
fonts: document.fonts.status,
|
|
178
|
+
}));
|
|
179
|
+
const optionStates = await options.evaluateAll(nodes =>
|
|
180
|
+
nodes.map(node => {
|
|
181
|
+
const style = getComputedStyle(node);
|
|
182
|
+
const box = node.getBoundingClientRect();
|
|
183
|
+
return {
|
|
184
|
+
name: node.textContent?.replace(/\s+/g, ' ').trim(),
|
|
185
|
+
selected: node.getAttribute('aria-selected'),
|
|
186
|
+
disabled: node.getAttribute('aria-disabled'),
|
|
187
|
+
backgroundColor: style.backgroundColor,
|
|
188
|
+
color: style.color,
|
|
189
|
+
opacity: style.opacity,
|
|
190
|
+
geometry: {
|
|
191
|
+
x: box.x,
|
|
192
|
+
y: box.y,
|
|
193
|
+
width: box.width,
|
|
194
|
+
height: box.height,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
const semanticsPass =
|
|
200
|
+
optionStates.length === expectedCount &&
|
|
201
|
+
optionStates.filter(option => option.selected === 'true').length === 1 &&
|
|
202
|
+
optionStates.filter(option => option.disabled === 'true').length ===
|
|
203
|
+
expectedDisabled &&
|
|
204
|
+
optionStates.every(
|
|
205
|
+
option => option.geometry.width > 0 && option.geometry.height > 0,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const png = await page.screenshot({animations: 'disabled', fullPage: true});
|
|
209
|
+
const image = `${item.key}.png`;
|
|
210
|
+
const receipt = {
|
|
211
|
+
schemaVersion: 1,
|
|
212
|
+
expected: {
|
|
213
|
+
build: checkoutSha,
|
|
214
|
+
storyId: storyId(item),
|
|
215
|
+
mode: item.mode,
|
|
216
|
+
direction: item.direction,
|
|
217
|
+
theme: item.theme,
|
|
218
|
+
state: item.state,
|
|
219
|
+
viewport: item.viewport,
|
|
220
|
+
optionCount: expectedCount,
|
|
221
|
+
disabledCount: expectedDisabled,
|
|
222
|
+
selectedCount: 1,
|
|
223
|
+
},
|
|
224
|
+
observed: {
|
|
225
|
+
build: storybookSha,
|
|
226
|
+
...observed,
|
|
227
|
+
listRole: await list.getAttribute('role'),
|
|
228
|
+
listName: await list.getAttribute('aria-label'),
|
|
229
|
+
optionStates,
|
|
230
|
+
pageErrors,
|
|
231
|
+
},
|
|
232
|
+
image: {
|
|
233
|
+
file: image,
|
|
234
|
+
sha256: createHash('sha256').update(png).digest('hex'),
|
|
235
|
+
width: png.readUInt32BE(16),
|
|
236
|
+
height: png.readUInt32BE(20),
|
|
237
|
+
},
|
|
238
|
+
checks: {semanticsPass},
|
|
239
|
+
passed:
|
|
240
|
+
storybookSha === checkoutSha &&
|
|
241
|
+
pageErrors.length === 0 &&
|
|
242
|
+
observed.theme === item.theme &&
|
|
243
|
+
observed.mode === item.mode &&
|
|
244
|
+
observed.direction === item.direction &&
|
|
245
|
+
observed.viewport.width === item.viewport.width &&
|
|
246
|
+
observed.viewport.height === item.viewport.height &&
|
|
247
|
+
observed.reducedMotion &&
|
|
248
|
+
!observed.horizontalOverflow &&
|
|
249
|
+
observed.fonts === 'loaded' &&
|
|
250
|
+
semanticsPass,
|
|
251
|
+
};
|
|
252
|
+
fs.writeFileSync(path.join(OUTPUT, image), png);
|
|
253
|
+
fs.writeFileSync(
|
|
254
|
+
path.join(OUTPUT, `${item.key}.sensors.json`),
|
|
255
|
+
`${JSON.stringify(receipt, null, 2)}\n`,
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
await context.close();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
test('manifest is complete', async () => {
|
|
263
|
+
const receiptFiles = fs
|
|
264
|
+
.readdirSync(OUTPUT)
|
|
265
|
+
.filter(file => file.endsWith('.sensors.json'));
|
|
266
|
+
const receipts = receiptFiles.map(
|
|
267
|
+
(file): AuditReceipt =>
|
|
268
|
+
JSON.parse(
|
|
269
|
+
fs.readFileSync(path.join(OUTPUT, file), 'utf8'),
|
|
270
|
+
) as AuditReceipt,
|
|
271
|
+
);
|
|
272
|
+
const manifest = {
|
|
273
|
+
schemaVersion: 1,
|
|
274
|
+
component: 'core/CommandPaletteItem',
|
|
275
|
+
build: checkoutSha,
|
|
276
|
+
frames: cases.map(item => ({
|
|
277
|
+
key: item.key,
|
|
278
|
+
image: `${item.key}.png`,
|
|
279
|
+
receipt: `${item.key}.sensors.json`,
|
|
280
|
+
})),
|
|
281
|
+
};
|
|
282
|
+
fs.writeFileSync(
|
|
283
|
+
path.join(OUTPUT, 'manifest.json'),
|
|
284
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
285
|
+
);
|
|
286
|
+
expect(receiptFiles).toHaveLength(cases.length);
|
|
287
|
+
expect(
|
|
288
|
+
receipts
|
|
289
|
+
.filter(receipt => !receipt.passed)
|
|
290
|
+
.map(receipt => receipt.expected),
|
|
291
|
+
).toEqual([]);
|
|
292
|
+
});
|
|
@@ -96,14 +96,13 @@ export const docs = {
|
|
|
96
96
|
name: 'items',
|
|
97
97
|
type: 'ContextMenuOption[]',
|
|
98
98
|
description:
|
|
99
|
-
'
|
|
100
|
-
required: true,
|
|
99
|
+
'Data-driven menu entries. Pass exactly one of `items` or `menuContent`. Each entry is one of: an action item `{label, onClick?, icon?, isDisabled?, variant?, items?}` (nested `items` open a flyout in popover presentation and drill into a new view in bottom-sheet presentation; variant `"destructive"` renders it in the error color), a divider `{type: "divider"}`, or a section `{type: "section", title?, items: [...action items]}`.',
|
|
101
100
|
},
|
|
102
101
|
{
|
|
103
102
|
name: 'menuContent',
|
|
104
103
|
type: 'ReactNode',
|
|
105
104
|
description:
|
|
106
|
-
'Custom JSX menu content for compound mode.
|
|
105
|
+
'Custom JSX menu content for compound mode. Pass exactly one of `menuContent` or `items`.',
|
|
107
106
|
},
|
|
108
107
|
{
|
|
109
108
|
name: 'menuWidth',
|
|
@@ -56,6 +56,7 @@ import {MenuBottomSheetActionList} from '../DropdownMenu/MenuBottomSheetActionLi
|
|
|
56
56
|
import {renderDropdownItems} from '../DropdownMenu/renderDropdownItems';
|
|
57
57
|
import {
|
|
58
58
|
DropdownMenuContext,
|
|
59
|
+
DropdownMenuRootOpenContext,
|
|
59
60
|
type DropdownMenuContextValue,
|
|
60
61
|
} from '../DropdownMenu/DropdownMenuContext';
|
|
61
62
|
import {
|
|
@@ -701,7 +702,9 @@ export function ContextMenu({
|
|
|
701
702
|
style,
|
|
702
703
|
)}>
|
|
703
704
|
<DropdownMenuContext value={contextValue}>
|
|
704
|
-
{
|
|
705
|
+
<DropdownMenuRootOpenContext value={isOpen}>
|
|
706
|
+
{resolvedMenuContent}
|
|
707
|
+
</DropdownMenuRootOpenContext>
|
|
705
708
|
</DropdownMenuContext>
|
|
706
709
|
</div>
|
|
707
710
|
);
|