@khanacademy/wonder-blocks-modal 3.0.2 → 3.0.4

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.
@@ -1,7 +1,5 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
- import {mount} from "enzyme";
4
- import "jest-enzyme";
5
3
  import {render, screen, waitFor} from "@testing-library/react";
6
4
  import userEvent from "@testing-library/user-event";
7
5
 
@@ -11,12 +9,6 @@ import Button from "@khanacademy/wonder-blocks-button";
11
9
  import ModalLauncher from "../modal-launcher.js";
12
10
  import OnePaneDialog from "../one-pane-dialog.js";
13
11
 
14
- import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
15
- import {getElementAttachedToDocument} from "../../../../../utils/testing/get-element-attached-to-document.js";
16
-
17
- const wait = (duration: number = 0) =>
18
- new Promise((resolve, reject) => setTimeout(resolve, duration));
19
-
20
12
  const exampleModal = (
21
13
  <OnePaneDialog
22
14
  title="Modal launcher test"
@@ -25,169 +17,150 @@ const exampleModal = (
25
17
  );
26
18
 
27
19
  describe("ModalLauncher", () => {
28
- afterEach(() => {
29
- unmountAll();
30
- if (document.body) {
31
- document.body.innerHTML = "";
32
- }
33
- });
34
-
35
20
  window.scrollTo = jest.fn();
36
21
 
37
22
  test("Children can launch the modal", async () => {
38
23
  // Arrange
39
- // We need the elements in the DOM document, it seems, for this test
40
- // to work. Changing to testing-library will likely fix this.
41
- const containerDiv = getElementAttachedToDocument("container");
42
- const wrapper = mount(
43
- <ModalLauncher modal={exampleModal}>
24
+ render(
25
+ <ModalLauncher modal={exampleModal} testId="modal-launcher-portal">
44
26
  {({openModal}) => <button onClick={openModal} />}
45
27
  </ModalLauncher>,
46
- {attachTo: containerDiv},
47
28
  );
48
29
 
49
30
  // Act
50
- wrapper.find("button").simulate("click");
51
- await wait();
31
+ userEvent.click(screen.getByRole("button"));
52
32
 
53
- // eslint-disable-next-line testing-library/no-node-access
54
- const portal = global.document.querySelector(
55
- "[data-modal-launcher-portal]",
56
- );
33
+ const portal = screen.getByTestId("modal-launcher-portal");
57
34
 
58
35
  // Assert
59
- expect(portal).toBeInstanceOf(HTMLDivElement);
36
+ expect(portal).toBeInTheDocument();
60
37
  });
61
38
 
62
39
  test("Modal can be manually opened and closed", () => {
63
- const wrapper = mount(
40
+ // Arrange
41
+ const UnderTest = ({opened}: {|opened: boolean|}) => (
64
42
  <ModalLauncher
65
43
  modal={exampleModal}
66
- opened={false}
44
+ opened={opened}
67
45
  onClose={() => {}}
68
- />,
46
+ testId="modal-launcher-portal"
47
+ />
69
48
  );
70
- expect(wrapper.find("[data-modal-launcher-portal]")).not.toExist();
71
- wrapper.setProps({opened: true});
72
- expect(wrapper.find("[data-modal-launcher-portal]")).toExist();
73
- wrapper.setProps({opened: false});
74
- expect(wrapper.find("[data-modal-launcher-portal]")).not.toExist();
49
+ const {rerender} = render(<UnderTest opened={false} />);
50
+
51
+ // Act
52
+ expect(
53
+ screen.queryByTestId("modal-launcher-portal"),
54
+ ).not.toBeInTheDocument();
55
+ rerender(<UnderTest opened={true} />);
56
+ expect(screen.getByTestId("modal-launcher-portal")).toBeInTheDocument();
57
+ rerender(<UnderTest opened={false} />);
58
+ expect(
59
+ screen.queryByTestId("modal-launcher-portal"),
60
+ ).not.toBeInTheDocument();
75
61
  });
76
62
 
77
- test("Modal can close itself after launching", (done) => {
78
- let opened = false;
79
-
80
- // Once the modal mounts, we'll immediately self-close it on the next
81
- // tick, to test the children's ability to self-close. (We wait a tick
82
- // because the API for the `children` prop says not to call `closeModal`
83
- // while rendering.)
84
- //
85
- // NOTE(mdr): It would be nice to have this be, like, a close button
86
- // that closes when you click it. But that requires the button to
87
- // actually _mount_, which means we'd need to do a full DOM render
88
- // including `ModalLauncherPortal`, and that seems to be going
89
- // beyond the scope of this test. Really we just want to check that
90
- // this function receives a `closeModal` argument that works.
91
- const modalFn = ({closeModal}: {|closeModal: () => void|}) => {
92
- expect(opened).toBe(true);
93
- setTimeout(closeModal, 0);
94
- return exampleModal;
95
- };
63
+ test("Modal can close itself after launching", async () => {
64
+ // Arrange
65
+ const modalFn = ({closeModal}: {|closeModal: () => void|}) => (
66
+ <OnePaneDialog
67
+ title="Modal launcher test"
68
+ content={
69
+ <View>
70
+ <Button onClick={closeModal}>Close it!</Button>
71
+ </View>
72
+ }
73
+ />
74
+ );
96
75
 
97
- // Once the modal closes, we'll check that it _really_ closed, and
98
- // finish the test.
99
- const onClose = () => {
100
- wrapper.update();
101
- expect(wrapper.find("ModalBackdrop")).toHaveLength(0);
102
- done();
103
- };
76
+ const onCloseMock = jest.fn();
104
77
 
105
78
  // Mount the modal launcher. This shouldn't trigger any closing yet,
106
79
  // because we shouldn't be calling the `modal` function yet.
107
- const wrapper = mount(
108
- <ModalLauncher modal={modalFn} onClose={onClose}>
80
+ render(
81
+ <ModalLauncher
82
+ modal={modalFn}
83
+ onClose={onCloseMock}
84
+ testId="modal-launcher-portal"
85
+ >
109
86
  {({openModal}) => <button onClick={openModal} />}
110
87
  </ModalLauncher>,
111
88
  );
112
- expect(
113
- // eslint-disable-next-line testing-library/no-node-access
114
- global.document.querySelector("[data-modal-launcher-portal]"),
115
- ).toBeNull();
116
-
117
- // Launch the modal. This should trigger closing, because we'll call
118
- // the modal function.
119
- opened = true;
120
- wrapper.find("button").simulate("click");
121
- // eslint-disable-next-line testing-library/no-node-access
122
- const portal = global.document.querySelector(
123
- "[data-modal-launcher-portal]",
124
- );
125
- expect(portal instanceof HTMLDivElement).toBe(true);
89
+
90
+ userEvent.click(screen.getByRole("button"));
91
+
92
+ // wait until the modal is open
93
+ await screen.findByRole("dialog");
94
+
95
+ // Act
96
+ userEvent.click(screen.getByRole("button", {name: "Close it!"}));
97
+
98
+ // Assert
99
+ expect(onCloseMock).toHaveBeenCalled();
126
100
  });
127
101
 
128
102
  test("Pressing Escape closes the modal", async () => {
129
- // We mount into a real DOM, in order to simulate and capture real key
130
- // presses anywhere in the document.
131
- const wrapper = mount(
103
+ // Arrange
104
+ render(
132
105
  <ModalLauncher modal={exampleModal}>
133
106
  {({openModal}) => <button onClick={openModal} />}
134
107
  </ModalLauncher>,
135
108
  );
136
109
 
137
110
  // Launch the modal.
138
- wrapper.find("button").simulate("click");
139
- // eslint-disable-next-line testing-library/no-node-access
140
- expect(document.querySelector("[data-modal-child]")).toBeTruthy();
111
+ userEvent.click(screen.getByRole("button"));
141
112
 
113
+ // wait until the modal is open
114
+ await screen.findByRole("dialog");
115
+
116
+ // Act
142
117
  // Simulate an Escape keypress.
143
- const event: KeyboardEvent = (document.createEvent("Event"): any);
144
- // $FlowIgnore[cannot-write]
145
- event.key = "Escape";
146
- event.initEvent("keyup", true, true);
147
- document.dispatchEvent(event);
118
+ userEvent.keyboard("{esc}");
148
119
 
120
+ // Assert
149
121
  // Confirm that the modal is no longer mounted.
150
- //
151
- // NOTE(mdr): This might be fragile once React's async rendering lands.
152
- // I wonder if we'll be able to force synchronous rendering in unit
153
- // tests?
154
- // eslint-disable-next-line testing-library/no-node-access
155
- expect(document.querySelector("[data-modal-child]")).toBeFalsy();
122
+ await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
156
123
  });
157
124
 
158
- test("Disable scrolling when the modal is open", () => {
159
- let savedCloseModal: () => void = () => {
160
- throw new Error(`closeModal wasn't saved`);
161
- };
162
-
163
- // Rather than test this rigorously, we'll just check that a
164
- // ScrollDisabler is present, and trust ScrollDisabler to do its job.
165
- const wrapper = mount(
166
- <ModalLauncher
167
- modal={({closeModal}) => {
168
- savedCloseModal = closeModal;
169
- return exampleModal;
170
- }}
171
- >
125
+ test("Disable scrolling when the modal is open", async () => {
126
+ // Arrange
127
+ render(
128
+ <ModalLauncher modal={exampleModal}>
172
129
  {({openModal}) => <button onClick={openModal} />}
173
130
  </ModalLauncher>,
174
131
  );
175
132
 
176
- // When the modal isn't open yet, there should be no ScrollDisabler.
177
- expect(wrapper.find("ScrollDisabler")).toHaveLength(0);
178
-
133
+ // Act
179
134
  // Launch the modal.
180
- wrapper.find("button").simulate("click");
135
+ userEvent.click(screen.getByRole("button"));
181
136
 
137
+ // wait until the modal is open
138
+ await screen.findByRole("dialog");
139
+
140
+ // Assert
182
141
  // Now that the modal is open, there should be a ScrollDisabler.
183
- expect(wrapper.find("ScrollDisabler")).toHaveLength(1);
142
+ expect(document.body).toHaveStyle("overflow: hidden");
143
+ });
144
+
145
+ test("re-enable scrolling after the modal is closed", async () => {
146
+ // Arrange
147
+ render(
148
+ <ModalLauncher modal={exampleModal}>
149
+ {({openModal}) => <button onClick={openModal} />}
150
+ </ModalLauncher>,
151
+ );
152
+
153
+ // Launch the modal.
154
+ userEvent.click(screen.getByRole("button"));
155
+
156
+ await screen.findByRole("dialog");
184
157
 
185
158
  // Close the modal.
186
- savedCloseModal();
187
- wrapper.update();
159
+ userEvent.click(screen.getByRole("button", {name: "Close modal"}));
188
160
 
161
+ // Assert
189
162
  // Now that the modal is closed, there should be no ScrollDisabler.
190
- expect(wrapper.find("ScrollDisabler")).toHaveLength(0);
163
+ expect(document.body).not.toHaveStyle("overflow: hidden");
191
164
  });
192
165
 
193
166
  test("using `opened` and `children` should warn", () => {
@@ -247,20 +220,19 @@ describe("ModalLauncher", () => {
247
220
  // Arrange
248
221
  const onClose = jest.fn();
249
222
 
250
- // We use `mount` instead of `shallow` here, because the component's
251
- // click handler expects actual DOM events.
252
- const wrapper = mount(
223
+ render(
253
224
  <ModalLauncher
254
225
  onClose={onClose}
255
226
  modal={exampleModal}
256
227
  opened={true}
257
228
  backdropDismissEnabled={false}
229
+ testId="modal-launcher-backdrop"
258
230
  />,
259
231
  );
260
232
 
261
233
  // Act
262
- const backdrop = wrapper.find("[data-modal-launcher-portal]").first();
263
- backdrop.simulate("click");
234
+ const backdrop = screen.getByTestId("modal-launcher-backdrop");
235
+ userEvent.click(backdrop);
264
236
 
265
237
  // Assert
266
238
  expect(onClose).not.toHaveBeenCalled();
@@ -268,30 +240,42 @@ describe("ModalLauncher", () => {
268
240
 
269
241
  test("if modal is launched, move focus inside the modal", async () => {
270
242
  // Arrange
271
- const wrapper = mount(
272
- <ModalLauncher modal={exampleModal}>
243
+ render(
244
+ <ModalLauncher
245
+ modal={
246
+ <OnePaneDialog
247
+ title="Modal launcher test"
248
+ content={
249
+ <View>
250
+ <Button>Button in modal</Button>
251
+ </View>
252
+ }
253
+ />
254
+ }
255
+ >
273
256
  {({openModal}) => (
274
- <button onClick={openModal} data-last-focused-button />
257
+ <button onClick={openModal}>Open modal</button>
275
258
  )}
276
259
  </ModalLauncher>,
277
260
  );
278
261
 
279
- const lastButton = wrapper
280
- .find("[data-last-focused-button]")
281
- .getDOMNode();
262
+ const modalOpener = screen.getByRole("button", {name: "Open modal"});
282
263
  // force focus
283
- lastButton.focus();
264
+ modalOpener.focus();
284
265
 
285
266
  // Act
286
267
  // Launch the modal.
287
- wrapper.find("button").simulate("click");
268
+ userEvent.type(modalOpener, "{enter}");
288
269
 
289
- // wait for styles to be applied
290
- await wait();
270
+ // wait until the modal is open
271
+ await screen.findByRole("dialog");
291
272
 
292
273
  // Assert
293
- // eslint-disable-next-line testing-library/no-node-access
294
- expect(document.activeElement).not.toBe(lastButton);
274
+ await waitFor(() =>
275
+ expect(
276
+ screen.getByRole("button", {name: "Button in modal"}),
277
+ ).toHaveFocus(),
278
+ );
295
279
  });
296
280
 
297
281
  test("if modal is closed, return focus to the last element focused outside the modal", async () => {
@@ -428,7 +412,7 @@ describe("ModalLauncher", () => {
428
412
 
429
413
  test("testId should be added to the Backdrop", () => {
430
414
  // Arrange
431
- const wrapper = mount(
415
+ render(
432
416
  <ModalLauncher
433
417
  opened={true}
434
418
  onClose={jest.fn()}
@@ -438,9 +422,9 @@ describe("ModalLauncher", () => {
438
422
  );
439
423
 
440
424
  // Act
441
- const backdrop = wrapper.find("[data-modal-launcher-portal]").first();
425
+ const backdrop = screen.getByTestId("test-id-example");
442
426
 
443
427
  // Assert
444
- expect(backdrop.prop("testId")).toBe("test-id-example");
428
+ expect(backdrop).toBeInTheDocument();
445
429
  });
446
430
  });
@@ -1,8 +1,8 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import * as ReactDOM from "react-dom";
4
- import {mount} from "enzyme";
5
- import "jest-enzyme";
4
+ import {render, screen} from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
6
 
7
7
  import {ModalLauncherPortalAttributeName} from "./constants.js";
8
8
  import maybeGetPortalMountedModalHostElement from "./maybe-get-portal-mounted-modal-host-element.js";
@@ -30,13 +30,11 @@ describe("maybeGetPortalMountedModalHostElement", () => {
30
30
  </div>
31
31
  </div>
32
32
  );
33
- const wrapper = mount(nodes);
34
- const candidateElement = wrapper.find("button").at(0);
33
+ render(nodes);
34
+ const candidateElement = screen.getByRole("button");
35
35
 
36
36
  // Act
37
- const result = maybeGetPortalMountedModalHostElement(
38
- candidateElement.getDOMNode(),
39
- );
37
+ const result = maybeGetPortalMountedModalHostElement(candidateElement);
40
38
 
41
39
  // Assert
42
40
  expect(result).toBeFalsy();
@@ -63,13 +61,14 @@ describe("maybeGetPortalMountedModalHostElement", () => {
63
61
  />
64
62
  );
65
63
 
66
- const wrapper = mount(modal);
67
- const candidateElement = wrapper.find("button").at(0);
64
+ render(modal);
65
+ const candidateElement = screen.getByRole("button", {
66
+ name: "Candidate",
67
+ });
68
68
 
69
69
  // Act
70
- const result = maybeGetPortalMountedModalHostElement(
71
- candidateElement.getDOMNode(),
72
- );
70
+ const result =
71
+ maybeGetPortalMountedModalHostElement(candidateElement);
73
72
 
74
73
  // Assert
75
74
  expect(result).toBeFalsy();
@@ -101,8 +100,8 @@ describe("maybeGetPortalMountedModalHostElement", () => {
101
100
  )}
102
101
  </ModalLauncher>
103
102
  );
104
- const wrapper = mount(launcher);
105
- wrapper.find("button").simulate("click");
103
+ render(launcher);
104
+ userEvent.click(screen.getByRole("button"));
106
105
  };
107
106
 
108
107
  const actAndAssert = (node) => {