@khanacademy/wonder-blocks-dropdown 2.6.7 → 2.6.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-dropdown",
3
- "version": "2.6.7",
3
+ "version": "2.6.8",
4
4
  "design": "v1",
5
5
  "description": "Dropdown variants for Wonder Blocks.",
6
6
  "main": "dist/index.js",
@@ -2475,7 +2475,7 @@ exports[`wonder-blocks-dropdown example 16 1`] = `
2475
2475
  }
2476
2476
  >
2477
2477
  <button
2478
- aria-expanded="true"
2478
+ aria-expanded="false"
2479
2479
  aria-haspopup="listbox"
2480
2480
  className=""
2481
2481
  disabled={false}
@@ -880,7 +880,7 @@ describe("wonder-blocks-dropdown", () => {
880
880
  constructor() {
881
881
  super();
882
882
  this.state = {
883
- opened: true,
883
+ opened: false,
884
884
  selectedValue: null,
885
885
  };
886
886
  this.handleChange = this.handleChange.bind(this);
@@ -1,17 +1,12 @@
1
1
  //@flow
2
2
  import * as React from "react";
3
- import {mount} from "enzyme";
4
- import "jest-enzyme";
3
+ import {render, screen} from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
5
 
6
6
  import ActionItem from "../action-item.js";
7
7
  import OptionItem from "../option-item.js";
8
8
  import SeparatorItem from "../separator-item.js";
9
9
  import ActionMenu from "../action-menu.js";
10
- import DropdownOpener from "../dropdown-opener.js";
11
- import {keyCodes} from "../../util/constants.js";
12
- import ActionMenuOpenerCore from "../action-menu-opener-core.js";
13
-
14
- jest.mock("../dropdown-core-virtualized.js");
15
10
 
16
11
  describe("ActionMenu", () => {
17
12
  const onClick = jest.fn();
@@ -20,7 +15,7 @@ describe("ActionMenu", () => {
20
15
 
21
16
  it("opens the menu on mouse click", () => {
22
17
  // Arrange
23
- const menu = mount(
18
+ render(
24
19
  <ActionMenu
25
20
  menuText={"Action menu!"}
26
21
  testId="openTest"
@@ -34,16 +29,16 @@ describe("ActionMenu", () => {
34
29
  );
35
30
 
36
31
  // Act
37
- const opener = menu.find(DropdownOpener);
38
- opener.simulate("click");
32
+ const opener = screen.getByRole("button");
33
+ userEvent.click(opener);
39
34
 
40
35
  // Assert
41
- expect(menu.state("opened")).toBe(true);
36
+ expect(screen.getByRole("menu")).toBeInTheDocument();
42
37
  });
43
38
 
44
39
  it("opens the menu on enter", () => {
45
40
  // Arrange
46
- const menu = mount(
41
+ render(
47
42
  <ActionMenu
48
43
  menuText={"Action menu!"}
49
44
  testId="openTest"
@@ -57,17 +52,16 @@ describe("ActionMenu", () => {
57
52
  );
58
53
 
59
54
  // Act
60
- const opener = menu.find(DropdownOpener);
61
- opener.simulate("keydown", {keyCode: keyCodes.enter});
62
- opener.simulate("keyup", {keyCode: keyCodes.enter});
55
+ userEvent.tab();
56
+ userEvent.keyboard("{enter}");
63
57
 
64
58
  // Assert
65
- expect(menu.state("opened")).toBe(true);
59
+ expect(screen.getByRole("menu")).toBeInTheDocument();
66
60
  });
67
61
 
68
62
  it("closes itself on escape", () => {
69
63
  // Arrange
70
- const menu = mount(
64
+ render(
71
65
  <ActionMenu
72
66
  menuText={"Action menu!"}
73
67
  testId="openTest"
@@ -80,21 +74,19 @@ describe("ActionMenu", () => {
80
74
  </ActionMenu>,
81
75
  );
82
76
 
77
+ userEvent.tab();
78
+ userEvent.keyboard("{enter}");
79
+
83
80
  // Act
84
- const opener = menu.find(DropdownOpener);
85
- // open using the mouse
86
- opener.simulate("click");
87
- // use keyboard to simulate the ESC key
88
- opener.simulate("keydown", {keyCode: keyCodes.escape});
89
- opener.simulate("keyup", {keyCode: keyCodes.escape});
81
+ userEvent.keyboard("{escape}");
90
82
 
91
83
  // Assert
92
- expect(menu.state("opened")).toBe(false);
84
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
93
85
  });
94
86
 
95
87
  it("closes itself on tab", () => {
96
88
  // Arrange
97
- const menu = mount(
89
+ render(
98
90
  <ActionMenu
99
91
  menuText={"Action menu!"}
100
92
  testId="openTest"
@@ -107,21 +99,19 @@ describe("ActionMenu", () => {
107
99
  </ActionMenu>,
108
100
  );
109
101
 
102
+ userEvent.tab();
103
+ userEvent.keyboard("{enter}");
104
+
110
105
  // Act
111
- const opener = menu.find(DropdownOpener);
112
- // open using the mouse
113
- opener.simulate("click");
114
- // use keyboard to simulate the TAB key
115
- opener.simulate("keydown", {keyCode: keyCodes.tab});
116
- opener.simulate("keyup", {keyCode: keyCodes.tab});
106
+ userEvent.tab();
117
107
 
118
108
  // Assert
119
- expect(menu.state("opened")).toBe(false);
109
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
120
110
  });
121
111
 
122
112
  it("closes itself on an external mouse click", () => {
123
113
  // Arrange
124
- const menu = mount(
114
+ const {container} = render(
125
115
  <ActionMenu
126
116
  menuText={"Action menu!"}
127
117
  testId="openTest"
@@ -134,20 +124,21 @@ describe("ActionMenu", () => {
134
124
  </ActionMenu>,
135
125
  );
136
126
 
137
- // Act
138
- const opener = menu.find(DropdownOpener);
127
+ const opener = screen.getByRole("button");
139
128
  // open using the mouse
140
- opener.simulate("click");
129
+ userEvent.click(opener);
130
+
131
+ // Act
141
132
  // trigger body click
142
- document.dispatchEvent(new MouseEvent("mouseup"));
133
+ userEvent.click(container);
143
134
 
144
135
  // Assert
145
- expect(menu.state("opened")).toBe(false);
136
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
146
137
  });
147
138
 
148
- it("updates the aria-expanded value when opening and closing", () => {
139
+ it("updates the aria-expanded value when opening", () => {
149
140
  // Arrange
150
- const menu = mount(
141
+ render(
151
142
  <ActionMenu
152
143
  menuText={"Action menu!"}
153
144
  testId="openTest"
@@ -160,51 +151,43 @@ describe("ActionMenu", () => {
160
151
  </ActionMenu>,
161
152
  );
162
153
 
163
- let opener = menu.find(DropdownOpener);
164
- let openerCore = menu.find(ActionMenuOpenerCore);
165
- expect(openerCore.prop("opened")).toBe(false);
166
- expect(opener.find("[aria-expanded='false']")).toExist();
167
-
168
154
  // Act
169
- opener.simulate("keydown", {keyCode: keyCodes.enter});
170
- opener.simulate("keyup", {keyCode: keyCodes.enter});
171
- menu.update();
172
- openerCore = menu.find(ActionMenuOpenerCore);
173
- opener = menu.find(DropdownOpener);
155
+ const opener = screen.getByRole("button");
156
+ userEvent.click(opener);
174
157
 
175
158
  // Assert
176
- expect(openerCore.prop("opened")).toBe(true);
177
- expect(opener.find("[aria-expanded='true']")).toExist();
159
+ expect(opener).toHaveAttribute("aria-expanded", "true");
178
160
  });
179
161
 
180
162
  it("triggers actions", () => {
181
163
  // Arrange
182
164
  const onChange = jest.fn();
183
- const menu = mount(
165
+ render(
184
166
  <ActionMenu
185
167
  menuText={"Action menu!"}
186
168
  onChange={onChange}
187
- selectedValues={["toggle_a", "toggle_b"]}
169
+ selectedValues={["toggle_a"]}
188
170
  >
189
171
  <OptionItem label="Toggle A" value="toggle_a" />
190
172
  <OptionItem label="Toggle B" value="toggle_b" />
191
173
  </ActionMenu>,
192
174
  );
193
- menu.find(DropdownOpener).simulate("click");
175
+
176
+ userEvent.click(screen.getByRole("button"));
194
177
 
195
178
  // Act
196
179
  // toggle second OptionItem
197
- const optionItem = menu.find(OptionItem).at(1);
198
- optionItem.props().onToggle("toggle_b");
180
+ const optionItem = screen.getByText("Toggle B");
181
+ userEvent.click(optionItem);
199
182
 
200
183
  // Assert
201
- expect(onChange).toHaveBeenCalledWith(["toggle_a"]);
184
+ expect(onChange).toHaveBeenCalledWith(["toggle_a", "toggle_b"]);
202
185
  });
203
186
 
204
187
  it("toggles select items", () => {
205
188
  // Arrange
206
189
  const onChange = jest.fn();
207
- const menu = mount(
190
+ render(
208
191
  <ActionMenu
209
192
  menuText={"Action menu!"}
210
193
  onChange={onChange}
@@ -214,11 +197,11 @@ describe("ActionMenu", () => {
214
197
  <OptionItem label="Toggle B" value="toggle_b" />
215
198
  </ActionMenu>,
216
199
  );
217
- menu.find(DropdownOpener).simulate("click");
200
+ userEvent.click(screen.getByRole("button"));
218
201
 
219
202
  // Act
220
- const optionItem = menu.find(OptionItem).at(0);
221
- optionItem.props().onToggle("toggle_a");
203
+ const optionItem = screen.getByText("Toggle A");
204
+ userEvent.click(optionItem);
222
205
 
223
206
  // Assert
224
207
  expect(onChange).toHaveBeenCalledWith(["toggle_a"]);
@@ -227,7 +210,7 @@ describe("ActionMenu", () => {
227
210
  it("deselects selected OptionItems", () => {
228
211
  // Arrange
229
212
  const onChange = jest.fn();
230
- const menu = mount(
213
+ render(
231
214
  <ActionMenu
232
215
  menuText={"Action menu!"}
233
216
  onChange={onChange}
@@ -237,12 +220,13 @@ describe("ActionMenu", () => {
237
220
  <OptionItem label="Toggle B" value="toggle_b" />
238
221
  </ActionMenu>,
239
222
  );
240
- menu.find(DropdownOpener).simulate("click");
223
+
224
+ userEvent.click(screen.getByRole("button"));
241
225
 
242
226
  // Act
243
- // toggle second OptionItem
244
- const optionItem = menu.find(OptionItem).at(1);
245
- optionItem.props().onToggle("toggle_b");
227
+ // Deselect second OptionItem
228
+ const optionItem = screen.getByText("Toggle B");
229
+ userEvent.click(optionItem);
246
230
 
247
231
  // Assert
248
232
  expect(onChange).toHaveBeenCalledWith(["toggle_a"]);
@@ -250,7 +234,7 @@ describe("ActionMenu", () => {
250
234
 
251
235
  it("doesn't break with OptionItems but no onChange callback", () => {
252
236
  // Arrange
253
- const menu = mount(
237
+ render(
254
238
  <ActionMenu
255
239
  menuText={"Action menu!"}
256
240
  selectedValues={["toggle_a", "toggle_b"]}
@@ -259,21 +243,22 @@ describe("ActionMenu", () => {
259
243
  <OptionItem label="Toggle B" value="toggle_b" />
260
244
  </ActionMenu>,
261
245
  );
262
- menu.find(DropdownOpener).simulate("click");
246
+
247
+ userEvent.click(screen.getByRole("button"));
263
248
 
264
249
  // Act, Assert
265
250
  expect(() => {
266
251
  // toggle second OptionItem
267
252
  // eslint-disable-next-line no-console
268
- const optionItem = menu.find(OptionItem).at(1);
269
- optionItem.props().onToggle("toggle_b");
253
+ const optionItem = screen.getByText("Toggle B");
254
+ userEvent.click(optionItem);
270
255
  }).not.toThrow();
271
256
  });
272
257
 
273
258
  it("works with extra selected values", () => {
274
259
  // Arrange
275
260
  const onChange = jest.fn();
276
- const menu = mount(
261
+ render(
277
262
  <ActionMenu
278
263
  menuText={"Action menu!"}
279
264
  onChange={onChange}
@@ -283,68 +268,74 @@ describe("ActionMenu", () => {
283
268
  <OptionItem label="Toggle B" value="toggle_b" />
284
269
  </ActionMenu>,
285
270
  );
286
- menu.find(DropdownOpener).simulate("click");
271
+ userEvent.click(screen.getByRole("button"));
287
272
 
288
273
  // Act
289
274
  // toggle second OptionItem
290
- const optionItem = menu.find(OptionItem).at(0);
291
- optionItem.props().onToggle("toggle_a");
275
+ const optionItem = screen.getByText("Toggle A");
276
+ userEvent.click(optionItem);
292
277
 
293
278
  // Assert
294
279
  expect(onChange).toHaveBeenCalledWith(["toggle_z"]);
295
280
  });
296
281
 
297
282
  it("can have a menu with a single item", () => {
298
- // Arrange, Act
299
- const menu = mount(
283
+ // Arrange
284
+ render(
300
285
  <ActionMenu menuText={"Action menu!"}>
301
286
  <ActionItem label="Action" />
302
287
  </ActionMenu>,
303
288
  );
304
- menu.find(DropdownOpener).simulate("click");
289
+
290
+ // Act
291
+ userEvent.click(screen.getByRole("button"));
305
292
 
306
293
  // Assert
307
- expect(menu.find(ActionItem)).toHaveLength(1);
294
+ expect(screen.getAllByRole("menuitem")).toHaveLength(1);
308
295
  });
309
296
 
310
297
  it("can have a menu with no items", () => {
311
- // Arrange, Act
312
- const menu = mount(<ActionMenu menuText={"Action menu!"} />);
313
- menu.find(DropdownOpener).simulate("click");
298
+ // Arrange
299
+ render(<ActionMenu menuText={"Action menu!"} />);
300
+
301
+ // Act
302
+ userEvent.click(screen.getByRole("button"));
314
303
 
315
304
  // Assert
316
- expect(menu.find(ActionItem)).toHaveLength(0);
305
+ expect(screen.queryByRole("menuitem")).not.toBeInTheDocument();
317
306
  });
318
307
 
319
308
  it("can have falsy items", () => {
320
- // Arrange, Act
309
+ // Arrange
321
310
  const showDeleteAction = false;
322
- const menu = mount(
311
+ render(
323
312
  <ActionMenu menuText={"Action menu!"}>
324
313
  <ActionItem label="Create" />
325
314
  {showDeleteAction && <ActionItem label="Delete" />}
326
315
  </ActionMenu>,
327
316
  );
328
- menu.find(DropdownOpener).simulate("click");
317
+
318
+ // Act
319
+ userEvent.click(screen.getByRole("button"));
329
320
 
330
321
  // Assert
331
- expect(menu.find(ActionItem)).toHaveLength(1);
332
- expect(menu.find(ActionItem).first()).toHaveProp("label", "Create");
322
+ expect(screen.getAllByRole("menuitem")).toHaveLength(1);
323
+ expect(screen.getByText("Create")).toBeInTheDocument();
333
324
  });
334
325
 
335
326
  it("verifies testId is added to the opener", () => {
336
327
  // Arrange
337
- const menu = mount(
328
+ render(
338
329
  <ActionMenu menuText={"Action menu!"} testId="some-test-id">
339
330
  <ActionItem label="Create" />
340
331
  </ActionMenu>,
341
332
  );
342
333
 
343
334
  // Act
344
- const opener = menu.find(DropdownOpener).find("button");
335
+ const opener = screen.getByRole("button");
345
336
 
346
337
  // Assert
347
- expect(opener.prop("data-test-id")).toBe("some-test-id");
338
+ expect(opener).toHaveAttribute("data-test-id", "some-test-id");
348
339
  });
349
340
 
350
341
  describe("Controlled component", () => {
@@ -353,51 +344,39 @@ describe("ActionMenu", () => {
353
344
  onToggle?: (opened: boolean) => mixed,
354
345
  |};
355
346
 
356
- type State = {|
357
- opened?: boolean,
358
- |};
359
- class ControlledComponent extends React.Component<Props, State> {
360
- state: State = {
361
- opened: this.props.opened,
362
- };
363
-
364
- handleToggleMenu = (opened) => {
365
- this.setState({
366
- opened: opened,
367
- });
347
+ function ControlledComponent(props: Props) {
348
+ const [opened, setOpened] = React.useState(props.opened);
368
349
 
369
- this.props.onToggle && this.props.onToggle(opened);
350
+ const handleToggleMenu = (opened) => {
351
+ setOpened(opened);
352
+ props.onToggle && props.onToggle(opened);
370
353
  };
371
354
 
372
- render(): React.Node {
373
- return (
374
- <React.Fragment>
375
- <ActionMenu
376
- opened={this.state.opened}
377
- onToggle={this.handleToggleMenu}
378
- menuText={"Action menu!"}
379
- >
380
- <ActionItem label="Create" />
381
- <ActionItem label="Delete" />
382
- </ActionMenu>
383
- <button
384
- data-test-id="parent-button"
385
- onClick={() => this.handleToggleMenu(true)}
386
- />
387
- </React.Fragment>
388
- );
389
- }
355
+ return (
356
+ <React.Fragment>
357
+ <ActionMenu
358
+ opened={opened}
359
+ onToggle={handleToggleMenu}
360
+ menuText={"Action menu!"}
361
+ >
362
+ <ActionItem label="Create" />
363
+ <ActionItem label="Delete" />
364
+ </ActionMenu>
365
+ <button
366
+ data-test-id="parent-button"
367
+ onClick={() => handleToggleMenu(true)}
368
+ />
369
+ </React.Fragment>
370
+ );
390
371
  }
391
372
 
392
373
  it("opens the menu when the parent updates its state", () => {
393
374
  // Arrange
394
375
  const onToggleMock = jest.fn();
395
- const wrapper = mount(
396
- <ControlledComponent onToggle={onToggleMock} />,
397
- );
376
+ render(<ControlledComponent onToggle={onToggleMock} />);
398
377
 
399
378
  // Act
400
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
379
+ userEvent.click(screen.getByTestId("parent-button"));
401
380
 
402
381
  // Assert
403
382
  expect(onToggleMock).toHaveBeenCalledWith(true);
@@ -405,51 +384,37 @@ describe("ActionMenu", () => {
405
384
 
406
385
  it("closes the menu when the parent updates its state", () => {
407
386
  const onToggleMock = jest.fn();
408
- const wrapper = mount(
409
- <ControlledComponent onToggle={onToggleMock} />,
410
- );
387
+ render(<ControlledComponent onToggle={onToggleMock} />);
411
388
 
412
389
  // Act
413
390
  // open the menu from the outside
414
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
391
+ userEvent.click(screen.getByTestId("parent-button"));
415
392
  // click on first item
416
- wrapper.find(ActionItem).at(0).simulate("click");
393
+ userEvent.click(screen.getByText("Create"));
417
394
 
418
395
  // Assert
419
396
  expect(onToggleMock).toHaveBeenCalledWith(false);
420
397
  });
421
398
 
422
- it("opens the menu when the anchor is clicked once", () => {
423
- // Arrange
424
- const wrapper = mount(<ControlledComponent />);
425
-
426
- // Act
427
- // click on the anchor
428
- wrapper.find(DropdownOpener).simulate("click");
429
-
430
- // Assert
431
- expect(wrapper.find(ActionMenu).prop("opened")).toBe(true);
432
- });
433
-
434
399
  it("closes the menu when an option is clicked", () => {
435
400
  // Arrange
436
- const wrapper = mount(<ControlledComponent />);
401
+ render(<ControlledComponent />);
437
402
 
438
403
  // Act
439
404
  // open the menu from the outside
440
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
405
+ userEvent.click(screen.getByTestId("parent-button"));
441
406
  // pick an option to close the menu
442
- wrapper.find(ActionItem).at(0).simulate("click");
407
+ userEvent.click(screen.getByText("Create"));
443
408
 
444
409
  // Assert
445
- expect(wrapper.find(ActionMenu).prop("opened")).toBe(false);
410
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
446
411
  });
447
412
  });
448
413
 
449
414
  describe("Custom Opener", () => {
450
415
  it("opens the menu when clicking on the custom opener", () => {
451
416
  // Arrange
452
- const menu = mount(
417
+ render(
453
418
  <ActionMenu
454
419
  menuText={"Action menu!"}
455
420
  testId="openTest"
@@ -464,18 +429,17 @@ describe("ActionMenu", () => {
464
429
  );
465
430
 
466
431
  // Act
467
- const opener = menu.find(DropdownOpener);
468
- opener.simulate("click");
432
+ userEvent.click(screen.getByLabelText("Search"));
469
433
 
470
434
  // Assert
471
- expect(menu.state("opened")).toBe(true);
435
+ expect(screen.getByRole("menu")).toBeInTheDocument();
472
436
  });
473
437
 
474
438
  it("calls the custom onClick handler", () => {
475
439
  // Arrange
476
440
  const onClickMock = jest.fn();
477
441
 
478
- const menu = mount(
442
+ render(
479
443
  <ActionMenu
480
444
  menuText={"Action menu!"}
481
445
  testId="openTest"
@@ -490,8 +454,7 @@ describe("ActionMenu", () => {
490
454
  );
491
455
 
492
456
  // Act
493
- const opener = menu.find(DropdownOpener);
494
- opener.simulate("click");
457
+ userEvent.click(screen.getByLabelText("Search"));
495
458
 
496
459
  // Assert
497
460
  expect(onClickMock).toHaveBeenCalledTimes(1);
@@ -499,7 +462,7 @@ describe("ActionMenu", () => {
499
462
 
500
463
  it("verifies testId is passed from the custom opener", () => {
501
464
  // Arrange
502
- const menu = mount(
465
+ render(
503
466
  <ActionMenu
504
467
  onChange={onChange}
505
468
  menuText="Action menu!"
@@ -516,15 +479,15 @@ describe("ActionMenu", () => {
516
479
  );
517
480
 
518
481
  // Act
519
- const opener = menu.find(DropdownOpener).find("button");
482
+ const opener = screen.getByLabelText("Custom opener");
520
483
 
521
484
  // Assert
522
- expect(opener.prop("data-test-id")).toBe("custom-opener");
485
+ expect(opener).toHaveAttribute("data-test-id", "custom-opener");
523
486
  });
524
487
 
525
488
  it("verifies testId is not passed from the parent element", () => {
526
489
  // Arrange
527
- const menu = mount(
490
+ render(
528
491
  <ActionMenu
529
492
  onChange={onChange}
530
493
  menuText="Action menu!"
@@ -537,15 +500,15 @@ describe("ActionMenu", () => {
537
500
  );
538
501
 
539
502
  // Act
540
- const opener = menu.find(DropdownOpener).find("button");
503
+ const opener = screen.getByLabelText("Custom opener");
541
504
 
542
505
  // Assert
543
- expect(opener.prop("data-test-id")).not.toBeDefined();
506
+ expect(opener).not.toHaveAttribute("data-test-id");
544
507
  });
545
508
 
546
509
  it("passes the menu text to the custom opener", () => {
547
510
  // Arrange
548
- const menu = mount(
511
+ render(
549
512
  <ActionMenu
550
513
  menuText="Action menu!"
551
514
  testId="openTest"
@@ -566,13 +529,10 @@ describe("ActionMenu", () => {
566
529
  );
567
530
 
568
531
  // Act
569
- const opener = menu.find(DropdownOpener);
570
- // open dropdown
571
- opener.simulate("click");
572
- const openerElement = menu.find(`[data-test-id="custom-opener"]`);
532
+ const opener = screen.getByTestId("custom-opener");
573
533
 
574
534
  // Assert
575
- expect(openerElement).toHaveText("Action menu!");
535
+ expect(opener).toHaveTextContent("Action menu!");
576
536
  });
577
537
  });
578
538
  });