@astryxdesign/core 0.6.0 → 0.6.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.
- package/CHANGELOG.md +41 -3
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
- package/dist/Center/Center.d.ts +23 -16
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +7 -5
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +12 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -0
- package/dist/Field/InputClearButton.d.ts +2 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +16 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +12 -1
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Indicator/CheckboxIndicator.js +2 -2
- package/dist/Indicator/RadioIndicator.js +2 -2
- package/dist/Layer/layerStack.d.ts +10 -0
- package/dist/Layer/layerStack.d.ts.map +1 -1
- package/dist/Layer/layerStack.js +21 -9
- package/dist/Layer/useLayerDismissal.d.ts +2 -3
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
- package/dist/Layer/useLayerDismissal.js +2 -3
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +12 -2
- package/dist/Popover/usePopover.d.ts +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +4 -2
- package/dist/ProgressBar/ProgressBar.js +2 -2
- package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
- package/dist/ScrollableArea/ScrollableArea.js +144 -0
- package/dist/ScrollableArea/index.d.ts +11 -0
- package/dist/ScrollableArea/index.d.ts.map +1 -0
- package/dist/ScrollableArea/index.js +11 -0
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +19 -4
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +12 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +56 -20
- package/dist/astryx.css +15 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scrollGeometry.d.ts +24 -0
- package/dist/hooks/scrollGeometry.d.ts.map +1 -0
- package/dist/hooks/scrollGeometry.js +86 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
- package/dist/hooks/scrollOwnerRegistry.js +24 -0
- package/dist/hooks/useFocusTrap.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +22 -11
- package/dist/hooks/useScrollableArea.d.ts +51 -0
- package/dist/hooks/useScrollableArea.d.ts.map +1 -0
- package/dist/hooks/useScrollableArea.js +287 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +2 -6
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/derivedVarRegistry.js +1 -1
- package/dist/theme/localTokens.d.ts +8 -11
- package/dist/theme/localTokens.d.ts.map +1 -1
- package/dist/theme/localTokens.js +17 -71
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +4 -4
- package/dist/utils/themeProps.d.ts +10 -10
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +27 -10
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -2
- package/src/AppShell/AppShell.test.tsx +36 -0
- package/src/AppShell/AppShell.tsx +4 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +3 -1
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
- package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
- package/src/Center/Center.doc.mjs +32 -28
- package/src/Center/Center.spec.md +225 -0
- package/src/Center/Center.test.tsx +42 -4
- package/src/Center/Center.tsx +24 -17
- package/src/Chat/ChatSystemMessage.test.tsx +2 -9
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +4 -4
- package/src/DateInput/DateInput.tsx +15 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
- package/src/DateTimeInput/DateTimeInput.tsx +18 -7
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
- package/src/Field/Field.test.tsx +42 -0
- package/src/Field/Field.tsx +6 -0
- package/src/Field/InputClearButton.test.tsx +35 -1
- package/src/Field/InputClearButton.tsx +7 -3
- package/src/Field/PanelSearchInput.tsx +21 -8
- package/src/FieldStatus/FieldStatus.spec.md +27 -17
- package/src/FieldStatus/FieldStatus.test.tsx +7 -5
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
- package/src/FileInput/FileInput.tsx +10 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +4 -4
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Indicator/CheckboxIndicator.tsx +2 -2
- package/src/Indicator/Indicator.doc.mjs +2 -2
- package/src/Indicator/Indicator.test.tsx +1 -1
- package/src/Indicator/RadioIndicator.tsx +2 -2
- package/src/Layer/layerStack.ts +20 -9
- package/src/Layer/useLayerDismissal.ts +2 -3
- package/src/MultiSelector/MultiSelector.test.tsx +4 -4
- package/src/NavIcon/NavIcon.doc.mjs +4 -4
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +18 -7
- package/src/Popover/Popover.doc.mjs +10 -10
- package/src/Popover/Popover.spec.md +55 -65
- package/src/Popover/Popover.test.tsx +29 -0
- package/src/Popover/usePopover.doc.mjs +4 -4
- package/src/Popover/usePopover.tsx +7 -4
- package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
- package/src/ProgressBar/ProgressBar.test.tsx +1 -31
- package/src/ProgressBar/ProgressBar.tsx +2 -2
- package/src/RadioList/RadioList.test.tsx +5 -144
- package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
- package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
- package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
- package/src/ScrollableArea/ScrollableArea.tsx +259 -0
- package/src/ScrollableArea/index.ts +26 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
- package/src/Selector/Selector.test.tsx +4 -4
- package/src/Spinner/Spinner.test.tsx +0 -18
- package/src/StatusDot/StatusDot.doc.mjs +4 -4
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/TabList/TabList.test.tsx +5 -9
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
- package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
- package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
- package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
- package/src/Table/Table.doc.mjs +2 -2
- package/src/TextArea/TextArea.doc.mjs +4 -4
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.doc.mjs +2 -1
- package/src/TextInput/TextInput.test.tsx +94 -0
- package/src/TextInput/TextInput.tsx +22 -6
- package/src/TimeInput/TimeInput.tsx +18 -7
- package/src/Toast/ToastViewport.test.tsx +1 -39
- package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
- package/src/Typeahead/BaseTypeahead.spec.md +269 -0
- package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
- package/src/Typeahead/BaseTypeahead.tsx +99 -30
- package/src/hooks/index.ts +13 -0
- package/src/hooks/scrollGeometry.ts +155 -0
- package/src/hooks/scrollOwnerRegistry.ts +47 -0
- package/src/hooks/useFocusTrap.ts +22 -11
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
- package/src/hooks/useScrollableArea.doc.mjs +108 -0
- package/src/hooks/useScrollableArea.test.tsx +437 -0
- package/src/hooks/useScrollableArea.ts +469 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +65 -105
- package/src/theme/defineTheme.ts +3 -9
- package/src/theme/derivedVarRegistry.ts +1 -1
- package/src/theme/localTokens.ts +25 -96
- package/src/theme/publicThemeHelperContract.test.ts +2 -2
- package/src/theme/themeAdaptations.test.ts +16 -42
- package/src/theme/themeAdaptations.ts +6 -9
- package/src/utils/themeProps.test.ts +29 -10
- package/src/utils/themeProps.ts +36 -17
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file RadioGroup.a11y.chromium.spec.ts
|
|
5
|
+
* @input Uses the shared radio-group contract, Chromium harness, checked-in stories, inventory, and exact known failures
|
|
6
|
+
* @output Real-browser and accessibility-tree evidence for every current bound radio-group part
|
|
7
|
+
* @position Browser lane required by AST-013; it makes no real-AT claim.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
expect,
|
|
12
|
+
test,
|
|
13
|
+
type CDPSession,
|
|
14
|
+
type Locator,
|
|
15
|
+
type Page,
|
|
16
|
+
} from '@playwright/test';
|
|
17
|
+
import {
|
|
18
|
+
RADIO_GROUP_PATTERN,
|
|
19
|
+
blockingResults,
|
|
20
|
+
checkAccessibilitySpec,
|
|
21
|
+
formatFailures,
|
|
22
|
+
formatReport,
|
|
23
|
+
neverExercised,
|
|
24
|
+
summarize,
|
|
25
|
+
unmatchedKnownFailures,
|
|
26
|
+
type BindingResult,
|
|
27
|
+
} from '@astryxdesign/a11y-spec';
|
|
28
|
+
import {
|
|
29
|
+
createChromiumHarness,
|
|
30
|
+
holdMotionStill,
|
|
31
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_STORYBOOK_DIR,
|
|
34
|
+
serveStorybook,
|
|
35
|
+
type StaticServer,
|
|
36
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
37
|
+
import {RADIO_GROUP_KNOWN_FAILURES} from './RadioGroup.a11y.known-failures';
|
|
38
|
+
import {
|
|
39
|
+
RADIO_GROUP_BINDING_STATES,
|
|
40
|
+
type RadioGroupBindingRow,
|
|
41
|
+
type RadioGroupBindingState,
|
|
42
|
+
} from './RadioGroup.a11y.states';
|
|
43
|
+
|
|
44
|
+
let storybook: StaticServer;
|
|
45
|
+
|
|
46
|
+
test.beforeAll(async () => {
|
|
47
|
+
storybook = await serveStorybook(
|
|
48
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test.afterAll(async () => {
|
|
53
|
+
await storybook?.close();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function storyUrl(storyId: string): string {
|
|
57
|
+
return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function subjectFor(page: Page, state: RadioGroupBindingRow): Locator {
|
|
61
|
+
return page
|
|
62
|
+
.locator('#storybook-root')
|
|
63
|
+
.getByRole(state.facts.role, {
|
|
64
|
+
name: state.subjectName,
|
|
65
|
+
includeHidden: true,
|
|
66
|
+
})
|
|
67
|
+
.first();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function relatedFor(
|
|
71
|
+
page: Page,
|
|
72
|
+
state: RadioGroupBindingRow,
|
|
73
|
+
): Readonly<Record<string, Locator>> {
|
|
74
|
+
const metadata = state as RadioGroupBindingState;
|
|
75
|
+
return Object.fromEntries(
|
|
76
|
+
Object.entries(metadata.relations ?? {}).map(([relation, option]) => [
|
|
77
|
+
relation,
|
|
78
|
+
page
|
|
79
|
+
.locator('#storybook-root')
|
|
80
|
+
.getByRole(option.role, {name: option.name, includeHidden: true})
|
|
81
|
+
.first(),
|
|
82
|
+
]),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function mountState(
|
|
87
|
+
page: Page,
|
|
88
|
+
state: RadioGroupBindingRow,
|
|
89
|
+
): Promise<void> {
|
|
90
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
91
|
+
await holdMotionStill(page);
|
|
92
|
+
const root = page.locator('#storybook-root');
|
|
93
|
+
if ((state as RadioGroupBindingState).opensMenu === true) {
|
|
94
|
+
await root.getByRole('button', {name: 'Sort options'}).click();
|
|
95
|
+
}
|
|
96
|
+
await subjectFor(page, state).waitFor({state: 'attached'});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runState(
|
|
100
|
+
page: Page,
|
|
101
|
+
cdp: CDPSession,
|
|
102
|
+
state: RadioGroupBindingRow,
|
|
103
|
+
): Promise<BindingResult> {
|
|
104
|
+
let mounted = false;
|
|
105
|
+
return checkAccessibilitySpec({
|
|
106
|
+
spec: RADIO_GROUP_PATTERN,
|
|
107
|
+
binding: state.binding,
|
|
108
|
+
state: state.id,
|
|
109
|
+
facts: state.facts,
|
|
110
|
+
knownFailures: RADIO_GROUP_KNOWN_FAILURES,
|
|
111
|
+
mount: async () => {
|
|
112
|
+
if (!mounted || state.facts.selection === 'none') {
|
|
113
|
+
await mountState(page, state);
|
|
114
|
+
mounted = true;
|
|
115
|
+
}
|
|
116
|
+
return createChromiumHarness({
|
|
117
|
+
page,
|
|
118
|
+
subject: subjectFor(page, state),
|
|
119
|
+
cdp,
|
|
120
|
+
visibleLabel: state.facts.visibleLabel ? undefined : null,
|
|
121
|
+
related: relatedFor(page, state),
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
test('every binding fact matches the rendered semantic state', async ({
|
|
128
|
+
page,
|
|
129
|
+
}) => {
|
|
130
|
+
test.setTimeout(4 * 60 * 1000);
|
|
131
|
+
const cdp = await page.context().newCDPSession(page);
|
|
132
|
+
const wrong: string[] = [];
|
|
133
|
+
|
|
134
|
+
for (const state of RADIO_GROUP_BINDING_STATES) {
|
|
135
|
+
await mountState(page, state);
|
|
136
|
+
const harness = createChromiumHarness({
|
|
137
|
+
page,
|
|
138
|
+
subject: subjectFor(page, state),
|
|
139
|
+
cdp,
|
|
140
|
+
related: relatedFor(page, state),
|
|
141
|
+
});
|
|
142
|
+
const subject = await harness.subject();
|
|
143
|
+
const computed = await subject.computed();
|
|
144
|
+
|
|
145
|
+
if (computed.role !== state.facts.role) {
|
|
146
|
+
wrong.push(
|
|
147
|
+
`${state.id}: declares role=${state.facts.role}, browser exposes ${computed.role}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
state.facts.checked != null &&
|
|
152
|
+
computed.checked !== (state.facts.checked ? 'true' : 'false')
|
|
153
|
+
) {
|
|
154
|
+
wrong.push(
|
|
155
|
+
`${state.id}: declares checked=${state.facts.checked}, browser exposes ${computed.checked}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
state.facts.disabled != null &&
|
|
160
|
+
computed.disabled !== state.facts.disabled
|
|
161
|
+
) {
|
|
162
|
+
wrong.push(
|
|
163
|
+
`${state.id}: declares disabled=${state.facts.disabled}, browser exposes ${computed.disabled}`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const description =
|
|
167
|
+
computed.description.trim() === '' ? null : computed.description;
|
|
168
|
+
if (description !== state.facts.description) {
|
|
169
|
+
wrong.push(
|
|
170
|
+
`${state.id}: declares description=${JSON.stringify(state.facts.description)}, browser exposes ${JSON.stringify(description)}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const required =
|
|
174
|
+
(await subject.attribute('required')) != null ||
|
|
175
|
+
(await subject.attribute('aria-required')) === 'true';
|
|
176
|
+
if (required !== state.facts.required) {
|
|
177
|
+
wrong.push(
|
|
178
|
+
`${state.id}: declares required=${state.facts.required}, page exposes ${required}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (computed.invalid !== state.facts.invalid) {
|
|
182
|
+
wrong.push(
|
|
183
|
+
`${state.id}: declares invalid=${state.facts.invalid}, browser exposes ${computed.invalid}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await harness.resetFocus();
|
|
188
|
+
let reachedByTab = false;
|
|
189
|
+
for (let step = 0; step < 10 && !reachedByTab; step += 1) {
|
|
190
|
+
await harness.press('Tab');
|
|
191
|
+
reachedByTab =
|
|
192
|
+
state.facts.part === 'group'
|
|
193
|
+
? await subject.containsFocus()
|
|
194
|
+
: await subject.isFocused();
|
|
195
|
+
}
|
|
196
|
+
if (reachedByTab !== state.facts.tabReachable) {
|
|
197
|
+
wrong.push(
|
|
198
|
+
`${state.id}: declares tabReachable=${state.facts.tabReachable}, page exposes ${reachedByTab}`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
expect(wrong).toEqual([]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('every expectation is exercised by at least one bound state', async ({
|
|
207
|
+
page,
|
|
208
|
+
}) => {
|
|
209
|
+
test.setTimeout(6 * 60 * 1000);
|
|
210
|
+
const cdp = await page.context().newCDPSession(page);
|
|
211
|
+
const results: BindingResult[] = [];
|
|
212
|
+
for (const state of RADIO_GROUP_BINDING_STATES) {
|
|
213
|
+
let mounted = false;
|
|
214
|
+
results.push(
|
|
215
|
+
await checkAccessibilitySpec({
|
|
216
|
+
spec: RADIO_GROUP_PATTERN,
|
|
217
|
+
binding: state.binding,
|
|
218
|
+
state: state.id,
|
|
219
|
+
facts: state.facts,
|
|
220
|
+
knownFailures: RADIO_GROUP_KNOWN_FAILURES,
|
|
221
|
+
mount: async () => {
|
|
222
|
+
if (!mounted || state.facts.selection === 'none') {
|
|
223
|
+
await mountState(page, state);
|
|
224
|
+
mounted = true;
|
|
225
|
+
}
|
|
226
|
+
return createChromiumHarness({
|
|
227
|
+
page,
|
|
228
|
+
subject: subjectFor(page, state),
|
|
229
|
+
cdp,
|
|
230
|
+
visibleLabel: state.facts.visibleLabel ? undefined : null,
|
|
231
|
+
related: relatedFor(page, state),
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
expect(neverExercised(results)).toEqual([]);
|
|
238
|
+
expect(unmatchedKnownFailures(RADIO_GROUP_KNOWN_FAILURES, results)).toEqual(
|
|
239
|
+
[],
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
for (const state of RADIO_GROUP_BINDING_STATES) {
|
|
244
|
+
test(`${state.binding} [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
245
|
+
test.setTimeout(3 * 60 * 1000);
|
|
246
|
+
const cdp = await page.context().newCDPSession(page);
|
|
247
|
+
const result = await runState(page, cdp, state);
|
|
248
|
+
const report = summarize(RADIO_GROUP_PATTERN, [result]);
|
|
249
|
+
// eslint-disable-next-line no-console -- the report is this run's artifact
|
|
250
|
+
console.log(formatReport(report));
|
|
251
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
252
|
+
expect(report.unrunLayers).toEqual([]);
|
|
253
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file RadioGroup.a11y.known-failures.ts
|
|
5
|
+
* @input Uses KnownFailure from @astryxdesign/a11y-spec
|
|
6
|
+
* @output Exact public-safe radio-group migration failures
|
|
7
|
+
* @position AST-021 debt records; operational ownership remains outside public source.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export const RADIO_GROUP_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file RadioGroup.a11y.renders.tsx
|
|
7
|
+
* @input Uses every current Astryx radio-group adopter and the checked state inventory
|
|
8
|
+
* @output Consumer-realistic render functions shared by jsdom and Storybook
|
|
9
|
+
* @position Binding fixture layer; callbacks, form behavior, composition, and styling stay local.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {useState, type ReactElement} from 'react';
|
|
13
|
+
import {
|
|
14
|
+
DropdownMenu,
|
|
15
|
+
DropdownMenuRadioGroup,
|
|
16
|
+
DropdownMenuRadioItem,
|
|
17
|
+
} from '../../DropdownMenu';
|
|
18
|
+
import {SegmentedControl, SegmentedControlItem} from '../../SegmentedControl';
|
|
19
|
+
import {RadioList, RadioListItem} from '../index';
|
|
20
|
+
import type {RadioGroupStateId} from './RadioGroup.a11y.states';
|
|
21
|
+
|
|
22
|
+
function RadioListFixture({
|
|
23
|
+
initial = 'standard',
|
|
24
|
+
orientation = 'horizontal',
|
|
25
|
+
direction = 'ltr',
|
|
26
|
+
description,
|
|
27
|
+
isRequired = false,
|
|
28
|
+
isInvalid = false,
|
|
29
|
+
isDisabled = false,
|
|
30
|
+
disabledMessage,
|
|
31
|
+
disabledOption,
|
|
32
|
+
}: {
|
|
33
|
+
initial?: string;
|
|
34
|
+
orientation?: 'horizontal' | 'vertical';
|
|
35
|
+
direction?: 'ltr' | 'rtl';
|
|
36
|
+
description?: string;
|
|
37
|
+
isRequired?: boolean;
|
|
38
|
+
isInvalid?: boolean;
|
|
39
|
+
isDisabled?: boolean;
|
|
40
|
+
disabledMessage?: string;
|
|
41
|
+
disabledOption?: 'express';
|
|
42
|
+
}) {
|
|
43
|
+
const [value, setValue] = useState(initial);
|
|
44
|
+
return (
|
|
45
|
+
<div dir={direction}>
|
|
46
|
+
<RadioList
|
|
47
|
+
label="Delivery speed"
|
|
48
|
+
description={description}
|
|
49
|
+
value={value}
|
|
50
|
+
onChange={setValue}
|
|
51
|
+
orientation={orientation}
|
|
52
|
+
isRequired={isRequired}
|
|
53
|
+
isDisabled={isDisabled}
|
|
54
|
+
disabledMessage={disabledMessage}
|
|
55
|
+
status={
|
|
56
|
+
isInvalid
|
|
57
|
+
? {type: 'error', message: 'Please select an option.'}
|
|
58
|
+
: undefined
|
|
59
|
+
}>
|
|
60
|
+
<RadioListItem
|
|
61
|
+
label="Standard"
|
|
62
|
+
value="standard"
|
|
63
|
+
description={
|
|
64
|
+
description == null ? undefined : 'Arrives in three to five days'
|
|
65
|
+
}
|
|
66
|
+
/>
|
|
67
|
+
<RadioListItem
|
|
68
|
+
label="Express"
|
|
69
|
+
value="express"
|
|
70
|
+
isDisabled={disabledOption === 'express'}
|
|
71
|
+
/>
|
|
72
|
+
<RadioListItem label="Overnight" value="overnight" />
|
|
73
|
+
</RadioList>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function SegmentedFixture({
|
|
79
|
+
initial = 'list',
|
|
80
|
+
direction = 'ltr',
|
|
81
|
+
isDisabled = false,
|
|
82
|
+
disabledMessage,
|
|
83
|
+
disabledOption,
|
|
84
|
+
hideSelectedLabel = false,
|
|
85
|
+
}: {
|
|
86
|
+
initial?: string;
|
|
87
|
+
direction?: 'ltr' | 'rtl';
|
|
88
|
+
isDisabled?: boolean;
|
|
89
|
+
disabledMessage?: string;
|
|
90
|
+
disabledOption?: 'grid';
|
|
91
|
+
hideSelectedLabel?: boolean;
|
|
92
|
+
}) {
|
|
93
|
+
const [value, setValue] = useState(initial);
|
|
94
|
+
return (
|
|
95
|
+
<div dir={direction}>
|
|
96
|
+
<SegmentedControl
|
|
97
|
+
value={value}
|
|
98
|
+
onChange={setValue}
|
|
99
|
+
label="View mode"
|
|
100
|
+
isDisabled={isDisabled}
|
|
101
|
+
disabledMessage={disabledMessage}>
|
|
102
|
+
<SegmentedControlItem
|
|
103
|
+
value="list"
|
|
104
|
+
label="List"
|
|
105
|
+
isLabelHidden={hideSelectedLabel}
|
|
106
|
+
icon={
|
|
107
|
+
hideSelectedLabel ? <span aria-hidden="true">≡</span> : undefined
|
|
108
|
+
}
|
|
109
|
+
/>
|
|
110
|
+
<SegmentedControlItem
|
|
111
|
+
value="grid"
|
|
112
|
+
label="Grid"
|
|
113
|
+
isDisabled={disabledOption === 'grid'}
|
|
114
|
+
/>
|
|
115
|
+
<SegmentedControlItem value="table" label="Table" />
|
|
116
|
+
</SegmentedControl>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function MenuRadioFixture({
|
|
122
|
+
initial = 'newest',
|
|
123
|
+
described = false,
|
|
124
|
+
disabledOption,
|
|
125
|
+
}: {
|
|
126
|
+
initial?: string;
|
|
127
|
+
described?: boolean;
|
|
128
|
+
disabledOption?: 'oldest';
|
|
129
|
+
}) {
|
|
130
|
+
const [value, setValue] = useState<string | undefined>(initial || undefined);
|
|
131
|
+
return (
|
|
132
|
+
<DropdownMenu button={{label: 'Sort options'}}>
|
|
133
|
+
<DropdownMenuRadioGroup
|
|
134
|
+
value={value}
|
|
135
|
+
onChange={setValue}
|
|
136
|
+
label="Sort by"
|
|
137
|
+
hasCloseOnSelect={false}>
|
|
138
|
+
<DropdownMenuRadioItem value="newest" label="Newest" />
|
|
139
|
+
<DropdownMenuRadioItem
|
|
140
|
+
value="oldest"
|
|
141
|
+
label="Oldest"
|
|
142
|
+
description={described ? 'Oldest items first' : undefined}
|
|
143
|
+
isDisabled={disabledOption === 'oldest'}
|
|
144
|
+
/>
|
|
145
|
+
<DropdownMenuRadioItem value="popular" label="Most viewed" />
|
|
146
|
+
</DropdownMenuRadioGroup>
|
|
147
|
+
</DropdownMenu>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type RadioGroupStateRender = () => ReactElement;
|
|
152
|
+
|
|
153
|
+
export const RADIO_GROUP_STATE_RENDERS: Record<
|
|
154
|
+
RadioGroupStateId,
|
|
155
|
+
RadioGroupStateRender
|
|
156
|
+
> = {
|
|
157
|
+
'radio-list-group-selected-ltr': () => <RadioListFixture />,
|
|
158
|
+
'radio-list-group-selected-rtl': () => <RadioListFixture direction="rtl" />,
|
|
159
|
+
'radio-list-group-selected-vertical': () => (
|
|
160
|
+
<RadioListFixture orientation="vertical" />
|
|
161
|
+
),
|
|
162
|
+
'radio-list-group-none-selected': () => (
|
|
163
|
+
<RadioListFixture initial="" orientation="vertical" />
|
|
164
|
+
),
|
|
165
|
+
'radio-list-group-required-invalid-described': () => (
|
|
166
|
+
<RadioListFixture
|
|
167
|
+
initial=""
|
|
168
|
+
orientation="vertical"
|
|
169
|
+
description="Choose a delivery speed."
|
|
170
|
+
isRequired
|
|
171
|
+
isInvalid
|
|
172
|
+
/>
|
|
173
|
+
),
|
|
174
|
+
'radio-list-group-disabled-with-message': () => (
|
|
175
|
+
<RadioListFixture
|
|
176
|
+
isDisabled
|
|
177
|
+
disabledMessage="Managed by your administrator"
|
|
178
|
+
orientation="vertical"
|
|
179
|
+
/>
|
|
180
|
+
),
|
|
181
|
+
'radio-list-option-unselected-described': () => (
|
|
182
|
+
<RadioListFixture
|
|
183
|
+
initial="express"
|
|
184
|
+
description="Choose a delivery speed."
|
|
185
|
+
/>
|
|
186
|
+
),
|
|
187
|
+
'radio-list-option-selected': () => <RadioListFixture />,
|
|
188
|
+
'radio-list-option-disabled': () => (
|
|
189
|
+
<RadioListFixture disabledOption="express" />
|
|
190
|
+
),
|
|
191
|
+
'radio-list-option-group-disabled': () => (
|
|
192
|
+
<RadioListFixture isDisabled orientation="vertical" />
|
|
193
|
+
),
|
|
194
|
+
'radio-list-option-disabled-with-message': () => (
|
|
195
|
+
<RadioListFixture
|
|
196
|
+
isDisabled
|
|
197
|
+
disabledMessage="Managed by your administrator"
|
|
198
|
+
orientation="vertical"
|
|
199
|
+
/>
|
|
200
|
+
),
|
|
201
|
+
|
|
202
|
+
'segmented-group-selected-ltr': () => <SegmentedFixture />,
|
|
203
|
+
'segmented-group-selected-rtl': () => <SegmentedFixture direction="rtl" />,
|
|
204
|
+
'segmented-group-none-selected': () => (
|
|
205
|
+
<SegmentedFixture initial="not-an-option" />
|
|
206
|
+
),
|
|
207
|
+
'segmented-group-disabled': () => <SegmentedFixture isDisabled />,
|
|
208
|
+
'segmented-group-disabled-with-message': () => (
|
|
209
|
+
<SegmentedFixture
|
|
210
|
+
isDisabled
|
|
211
|
+
disabledMessage="Choose a project to switch views"
|
|
212
|
+
/>
|
|
213
|
+
),
|
|
214
|
+
'segmented-option-unselected': () => <SegmentedFixture />,
|
|
215
|
+
'segmented-option-selected-hidden-label': () => (
|
|
216
|
+
<SegmentedFixture hideSelectedLabel />
|
|
217
|
+
),
|
|
218
|
+
'segmented-option-disabled': () => <SegmentedFixture disabledOption="grid" />,
|
|
219
|
+
'segmented-option-group-disabled': () => <SegmentedFixture isDisabled />,
|
|
220
|
+
'segmented-option-group-disabled-with-message': () => (
|
|
221
|
+
<SegmentedFixture
|
|
222
|
+
isDisabled
|
|
223
|
+
disabledMessage="Choose a project to switch views"
|
|
224
|
+
/>
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
'menu-group-selected': () => <MenuRadioFixture />,
|
|
228
|
+
'menu-group-none-selected': () => <MenuRadioFixture initial="" />,
|
|
229
|
+
'menu-option-unselected-described': () => <MenuRadioFixture described />,
|
|
230
|
+
'menu-option-selected': () => <MenuRadioFixture />,
|
|
231
|
+
'menu-option-disabled': () => <MenuRadioFixture disabledOption="oldest" />,
|
|
232
|
+
};
|