@sproutsocial/seeds-react-menu 1.16.16 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.turbo/turbo-build.log +14 -14
  2. package/CHANGELOG.md +21 -0
  3. package/dist/esm/v3/index.js +90 -56
  4. package/dist/esm/v3/index.js.map +1 -1
  5. package/dist/v3/index.d.mts +18 -0
  6. package/dist/v3/index.d.ts +18 -0
  7. package/dist/v3/index.js +90 -56
  8. package/dist/v3/index.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/ActionMenu/ActionMenu.stories.tsx +1 -1
  11. package/src/Autocomplete/Autocomplete.stories.tsx +1 -1
  12. package/src/MenuContent/MenuContent.stories.tsx +1 -1
  13. package/src/MenuFooter/MenuFooter.stories.tsx +1 -1
  14. package/src/MenuGroup/MenuGroup.stories.tsx +1 -1
  15. package/src/MenuHeader/MenuHeader.stories.tsx +1 -1
  16. package/src/MenuItem/MenuItem.stories.tsx +1 -1
  17. package/src/MenuToggleButton/MenuToggleButton.stories.tsx +1 -1
  18. package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +1 -1
  19. package/src/NestedMenu/NestedMenu.stories.tsx +1 -1
  20. package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +1 -1
  21. package/src/SubmenuItem/SubmenuItem.stories.tsx +1 -1
  22. package/src/v2/Autocomplete/Autocomplete.stories.tsx +1 -1
  23. package/src/v2/Select/Select.stories.tsx +1 -1
  24. package/src/v3/ActionMenu.stories.tsx +1 -1
  25. package/src/v3/MenuButton.stories.tsx +1 -1
  26. package/src/v3/ModalStories.stories.tsx +1 -1
  27. package/src/v3/MultiCombobox.stories.tsx +1 -1
  28. package/src/v3/MultiCombobox.tsx +7 -0
  29. package/src/v3/MultiSearchableSelect.stories.tsx +1 -1
  30. package/src/v3/MultiSearchableSelect.tsx +7 -0
  31. package/src/v3/MultiSelect.stories.tsx +1 -1
  32. package/src/v3/MultiSelect.tsx +9 -1
  33. package/src/v3/SingleCombobox.stories.tsx +1 -1
  34. package/src/v3/SingleCombobox.tsx +7 -0
  35. package/src/v3/SingleSearchableSelect.stories.tsx +1 -1
  36. package/src/v3/SingleSearchableSelect.tsx +7 -0
  37. package/src/v3/SingleSelect.stories.tsx +1 -1
  38. package/src/v3/SingleSelect.tsx +9 -1
  39. package/src/v3/__tests__/ariaLabels.test.tsx +133 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-menu",
3
- "version": "1.16.16",
3
+ "version": "1.17.0",
4
4
  "description": "Seeds React Menu",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -42,7 +42,7 @@ type ActionMenuStoryArgs = TypeActionMenuProps & {
42
42
  type Story = StoryObj<ActionMenuStoryArgs>;
43
43
 
44
44
  const meta: Meta<ActionMenuStoryArgs> = {
45
- title: "Under Development/ActionMenu",
45
+ title: "Components/Menus/v1/ActionMenu",
46
46
  component: ActionMenu,
47
47
  argTypes: {
48
48
  "Trigger Appearance": {
@@ -25,7 +25,7 @@ import Button from "@sproutsocial/seeds-react-button";
25
25
  type CustomAutocompleteArgs = React.ComponentProps<typeof Autocomplete>;
26
26
 
27
27
  const meta: Meta<CustomAutocompleteArgs> = {
28
- title: "Under Development/Autocomplete",
28
+ title: "Components/Menus/v1/Autocomplete",
29
29
  component: Autocomplete,
30
30
  parameters: {
31
31
  controls: {
@@ -18,7 +18,7 @@ import {
18
18
  } from "../index";
19
19
 
20
20
  const meta: Meta<typeof MenuContent> = {
21
- title: "Under Development/Primitives/MenuContent",
21
+ title: "Components/Menus/v1/Primitives/MenuContent",
22
22
  component: MenuContent,
23
23
  };
24
24
 
@@ -31,7 +31,7 @@ type Story = StoryObj<MenuFooterStoryArgs>;
31
31
  const optionsArr = ["Button Example", "Link Example", "Text Example", "none"];
32
32
 
33
33
  const meta: Meta<MenuFooterStoryArgs> = {
34
- title: "Under Development/Primitives/MenuFooter",
34
+ title: "Components/Menus/v1/Primitives/MenuFooter",
35
35
  component: MenuFooter,
36
36
  parameters: {
37
37
  controls: { expanded: true, sort: "requiredFirst" },
@@ -17,7 +17,7 @@ import {
17
17
  } from "../index";
18
18
 
19
19
  const meta: Meta<typeof MenuGroup> = {
20
- title: "Under Development/Primitives/MenuGroup",
20
+ title: "Components/Menus/v1/Primitives/MenuGroup",
21
21
  component: MenuGroup,
22
22
  decorators: [
23
23
  (Story) => (
@@ -29,7 +29,7 @@ interface MenuHeaderStoryArgs extends TypeMenuHeaderProps {
29
29
  type Story = StoryObj<MenuHeaderStoryArgs>;
30
30
 
31
31
  const meta: Meta<MenuHeaderStoryArgs> = {
32
- title: "Under Development/Primitives/MenuHeader",
32
+ title: "Components/Menus/v1/Primitives/MenuHeader",
33
33
  component: MenuHeader,
34
34
  parameters: {
35
35
  controls: { expanded: true, sort: "requiredFirst" },
@@ -34,7 +34,7 @@ interface MenuItemStoryArgs extends TypeMenuItemProps {
34
34
  type Story = StoryObj<MenuItemStoryArgs>;
35
35
 
36
36
  const meta: Meta<MenuItemStoryArgs> = {
37
- title: "Under Development/Primitives/MenuItem",
37
+ title: "Components/Menus/v1/Primitives/MenuItem",
38
38
  component: MenuItem,
39
39
  decorators: [
40
40
  (Story) => (
@@ -10,7 +10,7 @@ import {
10
10
  } from "../index";
11
11
 
12
12
  const meta: Meta<typeof MenuToggleButton> = {
13
- title: "Under Development/Primitives/MenuToggleButton",
13
+ title: "Components/Menus/v1/Primitives/MenuToggleButton",
14
14
  component: MenuToggleButton,
15
15
  decorators: [
16
16
  (Story) => (
@@ -23,7 +23,7 @@ type TypeMultiSelectMenuStoryProps = TypeMultiSelectMenuProps & {
23
23
  };
24
24
 
25
25
  const meta: Meta<TypeMultiSelectMenuStoryProps> = {
26
- title: "Under Development/MultiSelectMenu",
26
+ title: "Components/Menus/v1/MultiSelectMenu",
27
27
  component: MultiSelectMenu,
28
28
  decorators: [
29
29
  (Story) => (
@@ -23,7 +23,7 @@ import { MotionConfig } from "motion/react";
23
23
  type CustomNestedMenuArgs = React.ComponentProps<typeof NestedMenu>;
24
24
 
25
25
  const meta: Meta<CustomNestedMenuArgs> = {
26
- title: "Under Development/NestedMenu",
26
+ title: "Components/Menus/v1/NestedMenu",
27
27
  component: NestedMenu,
28
28
  parameters: {
29
29
  controls: {
@@ -24,7 +24,7 @@ type CustomSingleSelectArgs = TypeSingleSelectMenuProps & {
24
24
  };
25
25
 
26
26
  const meta: Meta<CustomSingleSelectArgs> = {
27
- title: "Under Development/SingleSelectMenu",
27
+ title: "Components/Menus/v1/SingleSelectMenu",
28
28
  component: SingleSelectMenu,
29
29
  parameters: {
30
30
  controls: {
@@ -18,7 +18,7 @@ type SubmenuItemStoryArgs = TypeMenuItemProps & {
18
18
  type Story = StoryObj<SubmenuItemStoryArgs>;
19
19
 
20
20
  const meta: Meta<SubmenuItemStoryArgs> = {
21
- title: "Under Development/Submenu",
21
+ title: "Components/Menus/v1/Submenu",
22
22
  decorators: [(Story) => <Story />],
23
23
  };
24
24
 
@@ -15,7 +15,7 @@ import type { ToggleItem, GroupHeading } from "../Common-V2";
15
15
  type CustomAutocompleteArgs = React.ComponentProps<typeof MultiAutocomplete>;
16
16
 
17
17
  const meta: Meta<CustomAutocompleteArgs> = {
18
- title: "Really Under Development/Autocomplete",
18
+ title: "Components/Menus/v2/Autocomplete",
19
19
  component: MultiAutocomplete,
20
20
  parameters: {
21
21
  controls: {
@@ -17,7 +17,7 @@ import type { ToggleItem } from "../Common-V2";
17
17
  type CustomSelectArgs = React.ComponentProps<typeof MultiSelect>;
18
18
 
19
19
  const meta: Meta<CustomSelectArgs> = {
20
- title: "Really Under Development/Select",
20
+ title: "Components/Menus/v2/Select",
21
21
  component: MultiSelect,
22
22
  parameters: {
23
23
  controls: {
@@ -14,7 +14,7 @@ import {
14
14
  } from "./ActionMenu";
15
15
 
16
16
  const meta: Meta<typeof ActionMenu> = {
17
- title: "Really Under Development/V3/ActionMenu",
17
+ title: "Components/Menus/v3/ActionMenu",
18
18
  component: ActionMenu,
19
19
  decorators: [
20
20
  (Story) => (
@@ -6,7 +6,7 @@ import { MenuButton } from "./MenuButton";
6
6
  import { MenuItem } from "./ActionMenu";
7
7
 
8
8
  const meta: Meta<typeof MenuButton> = {
9
- title: "Really Under Development/V3/MenuButton",
9
+ title: "Components/Menus/v3/MenuButton",
10
10
  component: MenuButton,
11
11
  decorators: [
12
12
  (Story) => (
@@ -138,7 +138,7 @@ function AllComponents() {
138
138
  // ─── Meta ─────────────────────────────────────────────────────────────────────
139
139
 
140
140
  const meta: Meta = {
141
- title: "Really Under Development/V3/In Modal",
141
+ title: "Components/Menus/v3/In Modal",
142
142
  };
143
143
 
144
144
  export default meta;
@@ -18,7 +18,7 @@ import {
18
18
  } from "./storyData";
19
19
 
20
20
  const meta: Meta<typeof MultiCombobox> = {
21
- title: "Really Under Development/V3/Multi Combobox",
21
+ title: "Components/Menus/v3/Multi Combobox",
22
22
  component: MultiCombobox,
23
23
  };
24
24
 
@@ -74,6 +74,9 @@ interface MultiComboboxBaseProps {
74
74
  filter?: ((item: unknown, query: string) => boolean) | null;
75
75
  inputValue?: string;
76
76
  onInputValueChange?: (value: string) => void;
77
+ "aria-label"?: string;
78
+ /** Id of a visible label. Preferred over `aria-label`. */
79
+ "aria-labelledby"?: string;
77
80
  "aria-describedby"?: string;
78
81
  isInvalid?: boolean;
79
82
  required?: boolean;
@@ -150,6 +153,8 @@ export function MultiCombobox<T extends DataWithId>(
150
153
  fullWidth = true,
151
154
  } = props;
152
155
  const ariaDescribedBy = props["aria-describedby"];
156
+ const ariaLabel = props["aria-label"];
157
+ const ariaLabelledBy = props["aria-labelledby"];
153
158
  const isInvalid = props.isInvalid;
154
159
 
155
160
  const isChildrenMode = "children" in props && props.children != null;
@@ -674,6 +679,8 @@ export function MultiCombobox<T extends DataWithId>(
674
679
  <ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
675
680
  <ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
676
681
  <ComboboxList
682
+ aria-label={ariaLabel}
683
+ aria-labelledby={ariaLabelledBy}
677
684
  style={
678
685
  isVirtualized
679
686
  ? {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./storyData";
12
12
 
13
13
  const meta: Meta<typeof MultiSearchableSelect> = {
14
- title: "Really Under Development/V3/Multi Searchable Select",
14
+ title: "Components/Menus/v3/Multi Searchable Select",
15
15
  component: MultiSearchableSelect,
16
16
  };
17
17
 
@@ -79,6 +79,9 @@ interface MultiSearchableSelectBaseProps {
79
79
  loadingText?: string;
80
80
  disabled?: boolean;
81
81
  filter?: ((item: unknown, query: string) => boolean) | null;
82
+ "aria-label"?: string;
83
+ /** Id of a visible label. Preferred over `aria-label`. */
84
+ "aria-labelledby"?: string;
82
85
  "aria-describedby"?: string;
83
86
  isInvalid?: boolean;
84
87
  required?: boolean;
@@ -151,6 +154,8 @@ export function MultiSearchableSelect<T extends DataWithId>(
151
154
  fullWidth = true,
152
155
  } = props;
153
156
  const ariaDescribedBy = props["aria-describedby"];
157
+ const ariaLabel = props["aria-label"];
158
+ const ariaLabelledBy = props["aria-labelledby"];
154
159
  const isInvalid = props.isInvalid;
155
160
 
156
161
  const isChildrenMode = "children" in props && props.children != null;
@@ -609,6 +614,8 @@ export function MultiSearchableSelect<T extends DataWithId>(
609
614
  <ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
610
615
  <ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
611
616
  <SearchableSelectList
617
+ aria-label={ariaLabel}
618
+ aria-labelledby={ariaLabelledBy}
612
619
  style={
613
620
  isVirtualized
614
621
  ? {
@@ -12,7 +12,7 @@ import {
12
12
  } from "./storyData";
13
13
 
14
14
  const meta: Meta<typeof MultiSelect> = {
15
- title: "Really Under Development/V3/Multi Select",
15
+ title: "Components/Menus/v3/Multi Select",
16
16
  component: MultiSelect,
17
17
  };
18
18
 
@@ -43,6 +43,9 @@ interface MultiSelectBaseProps {
43
43
  id?: string;
44
44
  placeholder?: string;
45
45
  disabled?: boolean;
46
+ "aria-label"?: string;
47
+ /** Id of a visible label. Preferred over `aria-label`. */
48
+ "aria-labelledby"?: string;
46
49
  "aria-describedby"?: string;
47
50
  isInvalid?: boolean;
48
51
  required?: boolean;
@@ -93,6 +96,8 @@ export type MultiSelectProps<T extends DataWithId> =
93
96
  export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
94
97
  const { id, placeholder = "Select...", fullWidth = true } = props;
95
98
  const ariaDescribedBy = props["aria-describedby"];
99
+ const ariaLabel = props["aria-label"];
100
+ const ariaLabelledBy = props["aria-labelledby"];
96
101
  const isInvalid = props.isInvalid;
97
102
 
98
103
  const isChildrenMode = "children" in props && props.children != null;
@@ -232,7 +237,10 @@ export function MultiSelect<T extends DataWithId>(props: MultiSelectProps<T>) {
232
237
  <SelectPositioner sideOffset={8} alignItemWithTrigger={false}>
233
238
  <SelectPopup>
234
239
  <Select.ScrollUpArrow />
235
- <Select.List>
240
+ <Select.List
241
+ aria-label={ariaLabel}
242
+ aria-labelledby={ariaLabelledBy}
243
+ >
236
244
  {children
237
245
  ? children
238
246
  : groupBy
@@ -11,7 +11,7 @@ import {
11
11
  } from "./storyData";
12
12
 
13
13
  const meta: Meta<typeof SingleCombobox> = {
14
- title: "Really Under Development/V3/Single Combobox",
14
+ title: "Components/Menus/v3/Single Combobox",
15
15
  component: SingleCombobox,
16
16
  };
17
17
 
@@ -32,6 +32,9 @@ interface SingleComboboxBaseProps {
32
32
  emptyText?: string;
33
33
  disabled?: boolean;
34
34
  filter?: ((item: unknown, query: string) => boolean) | null;
35
+ "aria-label"?: string;
36
+ /** Id of a visible label. Preferred over `aria-label`. */
37
+ "aria-labelledby"?: string;
35
38
  "aria-describedby"?: string;
36
39
  isInvalid?: boolean;
37
40
  required?: boolean;
@@ -96,6 +99,8 @@ export function SingleCombobox<T extends DataWithId>(
96
99
  fullWidth = true,
97
100
  } = props;
98
101
  const ariaDescribedBy = props["aria-describedby"];
102
+ const ariaLabel = props["aria-label"];
103
+ const ariaLabelledBy = props["aria-labelledby"];
99
104
  const isInvalid = props.isInvalid;
100
105
 
101
106
  const isChildrenMode = "children" in props && props.children != null;
@@ -244,6 +249,8 @@ export function SingleCombobox<T extends DataWithId>(
244
249
  >
245
250
  <ComboboxEmpty>{emptyText}</ComboboxEmpty>
246
251
  <ComboboxList
252
+ aria-label={ariaLabel}
253
+ aria-labelledby={ariaLabelledBy}
247
254
  style={
248
255
  isVirtualized
249
256
  ? {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./storyData";
12
12
 
13
13
  const meta: Meta<typeof SingleSearchableSelect> = {
14
- title: "Really Under Development/V3/Single Searchable Select",
14
+ title: "Components/Menus/v3/Single Searchable Select",
15
15
  component: SingleSearchableSelect,
16
16
  };
17
17
 
@@ -41,6 +41,9 @@ interface SingleSearchableSelectBaseProps {
41
41
  loadingText?: string;
42
42
  disabled?: boolean;
43
43
  filter?: ((item: unknown, query: string) => boolean) | null;
44
+ "aria-label"?: string;
45
+ /** Id of a visible label. Preferred over `aria-label`. */
46
+ "aria-labelledby"?: string;
44
47
  "aria-describedby"?: string;
45
48
  isInvalid?: boolean;
46
49
  required?: boolean;
@@ -112,6 +115,8 @@ export function SingleSearchableSelect<T extends DataWithId>(
112
115
  fullWidth = true,
113
116
  } = props;
114
117
  const ariaDescribedBy = props["aria-describedby"];
118
+ const ariaLabel = props["aria-label"];
119
+ const ariaLabelledBy = props["aria-labelledby"];
115
120
  const isInvalid = props.isInvalid;
116
121
 
117
122
  const isChildrenMode = "children" in props && props.children != null;
@@ -278,6 +283,8 @@ export function SingleSearchableSelect<T extends DataWithId>(
278
283
  <ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
279
284
  <ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
280
285
  <SearchableSelectList
286
+ aria-label={ariaLabel}
287
+ aria-labelledby={ariaLabelledBy}
281
288
  style={
282
289
  isVirtualized
283
290
  ? {
@@ -12,7 +12,7 @@ import {
12
12
  } from "./storyData";
13
13
 
14
14
  const meta: Meta<typeof SingleSelect> = {
15
- title: "Really Under Development/V3/Single Select",
15
+ title: "Components/Menus/v3/Single Select",
16
16
  component: SingleSelect,
17
17
  };
18
18
 
@@ -39,6 +39,9 @@ interface SingleSelectBaseProps {
39
39
  placeholder?: string;
40
40
  includePlaceholderItem?: boolean;
41
41
  disabled?: boolean;
42
+ "aria-label"?: string;
43
+ /** Id of a visible label. Preferred over `aria-label`. */
44
+ "aria-labelledby"?: string;
42
45
  "aria-describedby"?: string;
43
46
  isInvalid?: boolean;
44
47
  required?: boolean;
@@ -85,6 +88,8 @@ export function SingleSelect<T extends DataWithId>(
85
88
  ) {
86
89
  const { id, placeholder, includePlaceholderItem, fullWidth = true } = props;
87
90
  const ariaDescribedBy = props["aria-describedby"];
91
+ const ariaLabel = props["aria-label"];
92
+ const ariaLabelledBy = props["aria-labelledby"];
88
93
  const isInvalid = props.isInvalid;
89
94
 
90
95
  const isChildrenMode = "children" in props && props.children != null;
@@ -193,7 +198,10 @@ export function SingleSelect<T extends DataWithId>(
193
198
  <SelectPositioner sideOffset={8} alignItemWithTrigger={false}>
194
199
  <SelectPopup>
195
200
  <Select.ScrollUpArrow />
196
- <Select.List>
201
+ <Select.List
202
+ aria-label={ariaLabel}
203
+ aria-labelledby={ariaLabelledBy}
204
+ >
197
205
  {children ? (
198
206
  children
199
207
  ) : groupBy ? (
@@ -0,0 +1,133 @@
1
+ import React from "react";
2
+ import {
3
+ fireEvent,
4
+ render,
5
+ screen,
6
+ } from "@sproutsocial/seeds-react-testing-library";
7
+ import { SingleSelect } from "../SingleSelect";
8
+ import { MultiSelect } from "../MultiSelect";
9
+ import { SingleCombobox } from "../SingleCombobox";
10
+ import { MultiCombobox } from "../MultiCombobox";
11
+ import { SingleSearchableSelect } from "../SingleSearchableSelect";
12
+ import { MultiSearchableSelect } from "../MultiSearchableSelect";
13
+ import { books, itemToString } from "../storyData";
14
+
15
+ // Base UI's Select.List and Combobox.List render role="listbox" with no name of
16
+ // their own. axe only reports that as aria-input-field-name once the trigger has
17
+ // dropped its aria-controls, which happens while the popup animates closed — a
18
+ // state jsdom does not reach. So these assert the property that fixes the rule:
19
+ // the listbox carries a name of its own, independent of the trigger.
20
+
21
+ const LABEL_ID = "book-label";
22
+ const VisibleLabel = () => <span id={LABEL_ID}>Book</span>;
23
+
24
+ const openSelectPopup = () => {
25
+ fireEvent.click(screen.getByRole("combobox"));
26
+ };
27
+
28
+ const openComboboxPopup = () => {
29
+ fireEvent.click(screen.getByRole("button", { name: "Open popup" }));
30
+ };
31
+
32
+ interface Labelling {
33
+ "aria-label"?: string;
34
+ "aria-labelledby"?: string;
35
+ }
36
+
37
+ interface Case {
38
+ name: string;
39
+ renderWith: (labelling: Labelling) => ReturnType<typeof render>;
40
+ openPopup: () => void;
41
+ }
42
+
43
+ const cases: Case[] = [
44
+ {
45
+ name: "SingleSelect",
46
+ renderWith: (labelling) =>
47
+ render(
48
+ <SingleSelect data={books} itemToString={itemToString} {...labelling} />
49
+ ),
50
+ openPopup: openSelectPopup,
51
+ },
52
+ {
53
+ name: "MultiSelect",
54
+ renderWith: (labelling) =>
55
+ render(
56
+ <MultiSelect data={books} itemToString={itemToString} {...labelling} />
57
+ ),
58
+ openPopup: openSelectPopup,
59
+ },
60
+ {
61
+ name: "SingleSearchableSelect",
62
+ renderWith: (labelling) =>
63
+ render(
64
+ <SingleSearchableSelect
65
+ data={books}
66
+ itemToString={itemToString}
67
+ {...labelling}
68
+ />
69
+ ),
70
+ openPopup: openSelectPopup,
71
+ },
72
+ {
73
+ name: "MultiSearchableSelect",
74
+ renderWith: (labelling) =>
75
+ render(
76
+ <MultiSearchableSelect
77
+ data={books}
78
+ itemToString={itemToString}
79
+ {...labelling}
80
+ />
81
+ ),
82
+ openPopup: openSelectPopup,
83
+ },
84
+ {
85
+ name: "SingleCombobox",
86
+ renderWith: (labelling) =>
87
+ render(
88
+ <SingleCombobox
89
+ data={books}
90
+ itemToString={itemToString}
91
+ {...labelling}
92
+ />
93
+ ),
94
+ openPopup: openComboboxPopup,
95
+ },
96
+ {
97
+ name: "MultiCombobox",
98
+ renderWith: (labelling) =>
99
+ render(
100
+ <MultiCombobox
101
+ data={books}
102
+ itemToString={itemToString}
103
+ {...labelling}
104
+ />
105
+ ),
106
+ openPopup: openComboboxPopup,
107
+ },
108
+ ];
109
+
110
+ describe.each(cases)("$name popup listbox", ({ renderWith, openPopup }) => {
111
+ test("is named by aria-label", () => {
112
+ renderWith({ "aria-label": "Book" });
113
+ openPopup();
114
+
115
+ expect(screen.getByRole("listbox", { name: "Book" })).toBeInTheDocument();
116
+ });
117
+
118
+ test("is named by aria-labelledby", () => {
119
+ render(<VisibleLabel />);
120
+ renderWith({ "aria-labelledby": LABEL_ID });
121
+ openPopup();
122
+
123
+ expect(screen.getByRole("listbox", { name: "Book" })).toBeInTheDocument();
124
+ });
125
+
126
+ test("is unnamed when neither is given", () => {
127
+ renderWith({});
128
+ openPopup();
129
+
130
+ expect(screen.getByRole("listbox")).not.toHaveAttribute("aria-label");
131
+ expect(screen.getByRole("listbox")).not.toHaveAttribute("aria-labelledby");
132
+ });
133
+ });