@sproutsocial/seeds-react-menu 0.2.0 → 0.2.2

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.
@@ -27,7 +27,7 @@ describe("MultiSelectMenu", () => {
27
27
  cleanup();
28
28
  });
29
29
 
30
- it("should render the menuItemSelectables when provided as children", async () => {
30
+ it("should render the MenuItems when provided as children", async () => {
31
31
  const { user } = render(
32
32
  <MultiSelectMenu
33
33
  menuToggleElement={
@@ -70,7 +70,7 @@ describe("MultiSelectMenu", () => {
70
70
  });
71
71
  });
72
72
 
73
- xit("should allow for multiple items to be selected", async () => {
73
+ it("should allow for multiple items to be selected", async () => {
74
74
  const { user } = render(
75
75
  <MultiSelectMenu
76
76
  menuToggleElement={
@@ -113,20 +113,22 @@ describe("MultiSelectMenu", () => {
113
113
 
114
114
  await waitFor(() => {
115
115
  // Check that the first item is selected
116
- expect(item1).toHaveAttribute("aria-checked", "true");
116
+ expect(item1).toHaveAttribute("aria-selected", "true");
117
117
  });
118
118
 
119
119
  // Select second item
120
- // TODO: can this be done in the same act as the first item?
121
120
  await user.click(item2);
122
121
 
123
122
  await waitFor(() => {
124
123
  // Check that the second item is selected
125
- expect(item2).toHaveAttribute("aria-checked", "true");
124
+ expect(item2).toHaveAttribute("aria-selected", "true");
126
125
  });
126
+
127
+ // check that first item is still selected
128
+ expect(item1).toHaveAttribute("aria-selected", "true");
127
129
  });
128
130
 
129
- xit("Should maintain selected state when closed and reopened", async () => {
131
+ it("Should maintain selected state when closed and reopened", async () => {
130
132
  const { user } = render(
131
133
  <MultiSelectMenu
132
134
  menuToggleElement={
@@ -167,27 +169,29 @@ describe("MultiSelectMenu", () => {
167
169
  await user.click(item1);
168
170
 
169
171
  await waitFor(() => {
170
- expect(item1).toHaveAttribute("aria-checked", "true");
172
+ expect(item1).toHaveAttribute("aria-selected", "true");
171
173
  });
172
174
 
173
175
  // Close Menu (MultiSelect does not close automatically)
174
176
  await user.click(toggleButton);
175
177
 
176
178
  // Make sure the menu is closed
177
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
179
+ await waitFor(() =>
180
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument()
181
+ );
178
182
 
179
183
  // Open menu again
180
184
  await user.click(toggleButton);
181
185
 
182
- await waitFor(() => {
183
- // Make sure the menu is open
184
- expect(screen.getByRole("listbox")).toBeInTheDocument();
185
- // Make sure the item is still selected
186
- expect(item1).toHaveAttribute("aria-checked", "true");
187
- });
186
+ // Make sure the menu is open
187
+ await waitFor(() =>
188
+ expect(screen.getByRole("listbox")).toBeInTheDocument()
189
+ );
190
+ // Make sure the item is still selected
191
+ expect(item1).toHaveAttribute("aria-selected", "true");
188
192
  });
189
193
 
190
- xit("should be able to navigate using the keyboard", async () => {
194
+ it("should be able to navigate using the keyboard", async () => {
191
195
  const { user } = render(
192
196
  <MultiSelectMenu
193
197
  menuToggleElement={
@@ -213,137 +217,139 @@ describe("MultiSelectMenu", () => {
213
217
  </MultiSelectMenu>
214
218
  );
215
219
 
216
- const toggleButton = screen.getByRole("button", { name: "testable menu" });
220
+ // Open Menu
221
+ await user.keyboard("{Tab}{Enter}");
222
+ expect(await screen.findByRole("listbox")).toBeInTheDocument();
217
223
 
218
- // Open Menu;
219
- fireEvent.focus(toggleButton);
220
- await user.keyboard("{Enter}");
224
+ // Check that the first item is highlighted
221
225
  await user.keyboard("{ArrowDown}");
222
-
223
- await waitFor(() => {
224
- // Check that the first item is highlighted
225
- const item1 = screen.getByRole("option", { name: "Item 1" });
226
- expect(item1).toHaveFocus();
227
- });
226
+ expect(screen.getByRole("listbox")).toHaveAttribute(
227
+ "aria-activedescendant",
228
+ screen.getByRole("option", { name: "Item 1" }).getAttribute("id")
229
+ );
228
230
 
229
231
  // Select the first item
230
232
  await user.keyboard("{Enter}");
231
- await user.keyboard("{ArrowDown}");
233
+
232
234
  // select the second item
235
+ await user.keyboard("{ArrowDown}");
233
236
  await user.keyboard(" ");
234
237
 
238
+ // Check that the second item is highlighted and selected
239
+ const item2 = screen.getByRole("option", { name: "Item 2" });
240
+ await waitFor(() => expect(item2).toHaveAttribute("aria-selected", "true"));
241
+ expect(screen.getByRole("listbox")).toHaveAttribute(
242
+ "aria-activedescendant",
243
+ item2.getAttribute("id")
244
+ );
245
+
246
+ // Check that the first item is still selected
247
+ const item1 = screen.getByRole("option", { name: "Item 1" });
248
+ expect(item1).toHaveAttribute("aria-selected", "true");
249
+ });
250
+
251
+ it("Should work with a initially selected items", async () => {
252
+ const { user } = render(
253
+ <MultiSelectMenu
254
+ menuToggleElement={
255
+ <MenuToggleButton aria-label="testable menu" role="button">
256
+ Testable Menu
257
+ </MenuToggleButton>
258
+ }
259
+ selectedItems={[menuItems[1], menuItems[2]]}
260
+ >
261
+ <MenuContent>
262
+ <MenuGroup>
263
+ {menuItems.map((item) => (
264
+ <MenuItem
265
+ key={item.id}
266
+ id={item.id}
267
+ data-testid={item.id}
268
+ label={item.label}
269
+ >
270
+ {item.label}
271
+ </MenuItem>
272
+ ))}
273
+ </MenuGroup>
274
+ </MenuContent>
275
+ </MultiSelectMenu>
276
+ );
277
+
278
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
279
+
280
+ const toggleButton = screen.getByRole("button", {
281
+ name: "testable menu",
282
+ });
283
+
284
+ // Open Menu
285
+ await user.click(toggleButton);
286
+
287
+ const item1 = screen.getByRole("option", { name: "Item 1" });
288
+ const item2 = screen.getByRole("option", { name: "Item 2" });
289
+ const item3 = screen.getByRole("option", { name: "Item 3" });
290
+
235
291
  await waitFor(() => {
236
- // Check that the second item is focused and selected
237
- const item2 = screen.getByRole("option", { name: "Item 2" });
238
- expect(item2).toHaveFocus();
239
- expect(item2).toBeChecked();
240
-
241
- // Check that the first item is still selected
242
- const item1 = screen.getByRole("menuitem", { name: "Item 1" });
243
- expect(item1).toBeChecked();
292
+ expect(item1).not.toHaveStyle("pointer-events: none");
244
293
  });
245
294
 
246
- xit("should stay open after menu item selection", async () => {
247
- const { user } = render(
248
- <MultiSelectMenu
249
- menuToggleElement={
250
- <MenuToggleButton aria-label="testable menu" role="button">
251
- Testable Menu
252
- </MenuToggleButton>
253
- }
254
- >
255
- <MenuContent>
256
- <MenuGroup>
257
- {menuItems.map((item) => (
258
- <MenuItem
259
- key={item.id}
260
- id={item.id}
261
- data-testid={item.id}
262
- label={item.label}
263
- >
264
- {item.label}
265
- </MenuItem>
266
- ))}
267
- </MenuGroup>
268
- </MenuContent>
269
- </MultiSelectMenu>
270
- );
271
-
272
- const menuContentContainer = screen.queryByRole("listbox");
273
-
274
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
275
-
276
- const toggleButton = screen.getByRole("button", {
277
- name: "testable menu",
278
- });
279
-
280
- // Open Menu
281
- await user.click(toggleButton);
282
-
283
- const item1 = screen.getByRole("option", { name: "Item 1" });
284
-
285
- await waitFor(() => {
286
- expect(item1).not.toHaveStyle("pointer-events: none");
287
- });
288
-
289
- // Select Item 1
290
- await user.click(item1);
291
-
292
- await waitFor(() => {
293
- expect(menuContentContainer).toBeInTheDocument();
294
- });
295
+ // Check that the first item is not selected
296
+ expect(item1).not.toHaveAttribute("aria-selected", "true");
297
+ // Check that the second item is selected
298
+ expect(item2).toHaveAttribute("aria-selected", "true");
299
+ // Check that the third item is selected
300
+ expect(item3).toHaveAttribute("aria-selected", "true");
301
+ });
302
+
303
+ it("should stay open after menu item selection", async () => {
304
+ const mockSelectedItemsChange = jest.fn();
305
+ const { user } = render(
306
+ <MultiSelectMenu
307
+ menuToggleElement={
308
+ <MenuToggleButton aria-label="testable menu" role="button">
309
+ Testable Menu
310
+ </MenuToggleButton>
311
+ }
312
+ onSelectedItemsChange={mockSelectedItemsChange}
313
+ >
314
+ <MenuContent>
315
+ <MenuGroup>
316
+ {menuItems.map((item) => (
317
+ <MenuItem
318
+ key={item.id}
319
+ id={item.id}
320
+ data-testid={item.id}
321
+ label={item.label}
322
+ >
323
+ {item.label}
324
+ </MenuItem>
325
+ ))}
326
+ </MenuGroup>
327
+ </MenuContent>
328
+ </MultiSelectMenu>
329
+ );
330
+
331
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
332
+
333
+ const toggleButton = screen.getByRole("button", {
334
+ name: "testable menu",
295
335
  });
296
336
 
297
- xit("Should work with a initially selected items", async () => {
298
- const { user } = render(
299
- <MultiSelectMenu
300
- menuToggleElement={
301
- <MenuToggleButton aria-label="testable menu" role="button">
302
- Testable Menu
303
- </MenuToggleButton>
304
- }
305
- defaultSelectedItems={[menuItems[1], menuItems[2]]}
306
- >
307
- <MenuContent>
308
- <MenuGroup>
309
- {menuItems.map((item) => (
310
- <MenuItem
311
- key={item.id}
312
- id={item.id}
313
- data-testid={item.id}
314
- label={item.label}
315
- >
316
- {item.label}
317
- </MenuItem>
318
- ))}
319
- </MenuGroup>
320
- </MenuContent>
321
- </MultiSelectMenu>
322
- );
323
-
324
- expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
325
-
326
- const toggleButton = screen.getByRole("button", {
327
- name: "testable menu",
328
- });
329
-
330
- // Open Menu
331
- await user.click(toggleButton);
332
-
333
- const item1 = screen.getByRole("option", { name: "Item 1" });
334
- const item2 = screen.getByRole("option", { name: "Item 2" });
335
- const item3 = screen.getByRole("option", { name: "Item 3" });
336
-
337
- await waitFor(() => {
338
- expect(item1).not.toHaveStyle("pointer-events: none");
339
- });
340
-
341
- act(() => {
342
- // Check that the second item is selected
343
- expect(item2).toHaveAttribute("aria-checked", "true");
344
- // Check that the third item is selected
345
- expect(item3).toHaveAttribute("aria-checked", "true");
346
- });
337
+ // Open Menu
338
+ await user.click(toggleButton);
339
+
340
+ const item1 = screen.getByRole("option", { name: "Item 1" });
341
+
342
+ await waitFor(() => {
343
+ expect(item1).not.toHaveStyle("pointer-events: none");
347
344
  });
345
+
346
+ // Select Item 1
347
+ await user.click(item1);
348
+
349
+ // Check that the menu is still open
350
+ expect(await screen.findByRole("listbox")).toBeInTheDocument();
351
+
352
+ // Check that callback called
353
+ expect(mockSelectedItemsChange).toHaveBeenCalledTimes(1);
348
354
  });
349
355
  });
@@ -1,10 +1,14 @@
1
1
  import React, { type ReactNode, useMemo } from "react";
2
- import type { TypeItemElements } from "../types";
2
+ import type { TypeMenuItemProps } from "../MenuItem";
3
3
  import type { TypeMenuContext } from "../MenuContext";
4
4
 
5
5
  export interface TypeUseItemsFromChildrenProps {
6
6
  children: ReactNode;
7
7
  }
8
+ export interface TypeItemElements {
9
+ element: React.ReactElement<TypeMenuItemProps>;
10
+ parents: readonly React.ReactNode[];
11
+ }
8
12
 
9
13
  // https://stackoverflow.com/questions/69119016/find-specific-children-components-regardless-of-depth-in-react
10
14
  const walkAllChildren = (
@@ -59,13 +63,12 @@ export const useItemsFromChildren = ({
59
63
  const allMenuItemElements = getAllMenuItems(children);
60
64
  const menuItemsMap: TypeMenuContext["itemsMap"] = {};
61
65
  const allMenuItems: TypeMenuContext["items"] = allMenuItemElements.map(
62
- ({ element, parents }, index) => {
66
+ ({ element }, index) => {
63
67
  const item = {
64
68
  id: element.props.id,
65
69
  disabled: element.props.disabled,
66
70
  index,
67
- element,
68
- parents,
71
+ menuItemProps: element.props,
69
72
  };
70
73
  menuItemsMap[`${item.id}`] = item;
71
74
  return item;
package/src/index.ts CHANGED
@@ -17,3 +17,4 @@ export * from "./MultiSelectMenu";
17
17
  export * from "./MenuContext";
18
18
  export * from "./hooks/useItemsFromChildren";
19
19
  export * from "./reducers/useSelectStateReducer";
20
+ export * from "./types";
package/src/types.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  import type { TypeMenuItemProps } from "./MenuItem";
2
2
 
3
- export interface TypeItemElements {
4
- element: React.ReactElement<TypeMenuItemProps>;
5
- parents: readonly React.ReactNode[];
6
- }
7
- export interface TypeDefaultItemProps extends Partial<TypeItemElements> {
3
+ export interface TypeDefaultItemProps {
8
4
  id: string;
9
5
  index: number;
10
6
  disabled?: boolean;
7
+ menuItemProps?: TypeMenuItemProps;
11
8
  }
12
9
 
13
10
  export type TypeNotEmptyChildren =