@khanacademy/wonder-blocks-modal 2.1.41 → 2.1.45

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-modal",
3
- "version": "2.1.41",
3
+ "version": "2.1.45",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,23 +15,24 @@
15
15
  "author": "",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.23",
19
- "@khanacademy/wonder-blocks-color": "^1.1.17",
20
- "@khanacademy/wonder-blocks-core": "^3.1.3",
21
- "@khanacademy/wonder-blocks-icon": "^1.2.20",
22
- "@khanacademy/wonder-blocks-icon-button": "^3.3.12",
23
- "@khanacademy/wonder-blocks-layout": "^1.4.2",
24
- "@khanacademy/wonder-blocks-spacing": "^3.0.2",
25
- "@khanacademy/wonder-blocks-toolbar": "^2.1.24",
26
- "@khanacademy/wonder-blocks-typography": "^1.1.24"
18
+ "@babel/runtime": "^7.16.3",
19
+ "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.27",
20
+ "@khanacademy/wonder-blocks-color": "^1.1.20",
21
+ "@khanacademy/wonder-blocks-core": "^4.0.0",
22
+ "@khanacademy/wonder-blocks-icon": "^1.2.24",
23
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.1",
24
+ "@khanacademy/wonder-blocks-layout": "^1.4.6",
25
+ "@khanacademy/wonder-blocks-spacing": "^3.0.5",
26
+ "@khanacademy/wonder-blocks-toolbar": "^2.1.28",
27
+ "@khanacademy/wonder-blocks-typography": "^1.1.28"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "aphrodite": "^1.2.5",
30
- "react": "^16.4.1",
31
- "react-dom": "^16.4.1"
31
+ "react": "16.14.0",
32
+ "react-dom": "16.14.0"
32
33
  },
33
34
  "devDependencies": {
34
- "wb-dev-build-settings": "^0.0.4"
35
+ "wb-dev-build-settings": "^0.2.0"
35
36
  },
36
- "gitHead": "0a09d6f092c205141387071394b2b0373c0f4294"
37
+ "gitHead": "9ebea88533e702011165072f090a377e02fa3f0f"
37
38
  }
@@ -5,7 +5,10 @@ import {mount} from "enzyme";
5
5
  import ModalBackdrop from "../modal-backdrop.js";
6
6
  import OnePaneDialog from "../one-pane-dialog.js";
7
7
 
8
- const sleep = (duration: number = 0) =>
8
+ import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
9
+ import {getElementAttachedToDocument} from "../../../../../utils/testing/get-element-attached-to-document.js";
10
+
11
+ const wait = (duration: number = 0) =>
9
12
  new Promise((resolve, reject) => setTimeout(resolve, duration));
10
13
 
11
14
  const exampleModal = (
@@ -31,6 +34,17 @@ const exampleModalWithButtons = (
31
34
  );
32
35
 
33
36
  describe("ModalBackdrop", () => {
37
+ beforeEach(() => {
38
+ jest.useRealTimers();
39
+ });
40
+
41
+ afterEach(() => {
42
+ unmountAll();
43
+ if (document.body) {
44
+ document.body.innerHTML = "";
45
+ }
46
+ });
47
+
34
48
  test("Clicking the backdrop triggers `onCloseModal`", () => {
35
49
  const onCloseModal = jest.fn();
36
50
 
@@ -80,6 +94,9 @@ describe("ModalBackdrop", () => {
80
94
 
81
95
  test("If initialFocusId is set and element is found, we focus that element inside the modal", async () => {
82
96
  // Arrange
97
+ // We need the elements in the DOM document, it seems, for this test
98
+ // to work. Changing to testing-library will likely fix this.
99
+ const attachElement = getElementAttachedToDocument("container");
83
100
  const initialFocusId = "initial-focus";
84
101
 
85
102
  const wrapper = mount(
@@ -98,10 +115,11 @@ describe("ModalBackdrop", () => {
98
115
  footer={<div data-modal-footer />}
99
116
  />
100
117
  </ModalBackdrop>,
118
+ {attachTo: attachElement},
101
119
  );
102
120
 
103
121
  // Act
104
- await sleep(); // wait for styles to be applied
122
+ await wait(); // wait for styles to be applied
105
123
  const initialFocusElement = wrapper.find(`#${initialFocusId}`);
106
124
 
107
125
  // Assert
@@ -113,6 +131,9 @@ describe("ModalBackdrop", () => {
113
131
 
114
132
  test("If initialFocusId is set but element is NOT found, we focus on the first focusable element instead", async () => {
115
133
  // Arrange
134
+ // We need the elements in the DOM document, it seems, for this test
135
+ // to work. Changing to testing-library will likely fix this.
136
+ const attachElement = getElementAttachedToDocument("container");
116
137
  const initialFocusId = "initial-focus";
117
138
  const firstFocusableElement = "[data-first-button]";
118
139
 
@@ -123,10 +144,11 @@ describe("ModalBackdrop", () => {
123
144
  >
124
145
  {exampleModalWithButtons}
125
146
  </ModalBackdrop>,
147
+ {attachTo: attachElement},
126
148
  );
127
149
 
128
150
  // Act
129
- await sleep(); // wait for styles to be applied
151
+ await wait(); // wait for styles to be applied
130
152
  const initialFocusElement = wrapper.find(`#${initialFocusId}`);
131
153
 
132
154
  // Assert
@@ -140,14 +162,18 @@ describe("ModalBackdrop", () => {
140
162
 
141
163
  test("If no initialFocusId is set, we focus the first button in the modal", async () => {
142
164
  // Arrange
165
+ // We need the elements in the DOM document, it seems, for this test
166
+ // to work. Changing to testing-library will likely fix this.
167
+ const attachElement = getElementAttachedToDocument("container");
143
168
  const wrapper = mount(
144
169
  <ModalBackdrop onCloseModal={() => {}}>
145
170
  {exampleModalWithButtons}
146
171
  </ModalBackdrop>,
172
+ {attachTo: attachElement},
147
173
  );
148
174
 
149
175
  // Act
150
- await sleep(); // wait for styles to be applied
176
+ await wait(); // wait for styles to be applied
151
177
  const focusableElement = wrapper
152
178
  .find("[data-first-button]")
153
179
  .getDOMNode();
@@ -158,14 +184,18 @@ describe("ModalBackdrop", () => {
158
184
 
159
185
  test("If there are no focusable elements, we focus the Dialog instead", async () => {
160
186
  // Arrange
187
+ // We need the elements in the DOM document, it seems, for this test
188
+ // to work. Changing to testing-library will likely fix this.
189
+ const attachElement = getElementAttachedToDocument("container");
161
190
  const wrapper = mount(
162
191
  <ModalBackdrop onCloseModal={() => {}}>
163
192
  {exampleModal}
164
193
  </ModalBackdrop>,
194
+ {attachTo: attachElement},
165
195
  );
166
196
 
167
197
  // Act
168
- await sleep(); // wait for styles to be applied
198
+ await wait(); // wait for styles to be applied
169
199
  const focusableElement = wrapper
170
200
  .find('div[role="dialog"]')
171
201
  .getDOMNode();
@@ -173,50 +203,4 @@ describe("ModalBackdrop", () => {
173
203
  // Assert
174
204
  expect(document.activeElement).toBe(focusableElement);
175
205
  });
176
-
177
- // TODO(mdr): I haven't figured out how to actually simulate tab keystrokes
178
- // or focus events in a way that JSDOM will recognize, so triggering the
179
- // global focus handler isn't feasible. I had to do manual testing
180
- // instead :( Here's what I had, though!
181
- test.skip("Tabbing inside the modal wraps around", () => {
182
- const wrapper = mount(
183
- <div>
184
- <button data-button-id="A" />
185
- <ModalBackdrop onCloseModal={() => {}}>
186
- {exampleModalWithButtons}
187
- </ModalBackdrop>
188
- <button data-button-id="Z" />
189
- </div>,
190
- );
191
-
192
- const buttonA = wrapper.find('[data-button-id="A"]').getDOMNode();
193
- const button1 = wrapper.find('[data-button-id="1"]').getDOMNode();
194
- const button2 = wrapper.find('[data-button-id="2"]').getDOMNode();
195
- const button3 = wrapper.find('[data-button-id="3"]').getDOMNode();
196
- const buttonZ = wrapper.find('[data-button-id="Z"]').getDOMNode();
197
-
198
- // First, go forward. Confirm that, when we get to button Z, we wrap
199
- // back to button 1. (I wish we could just simulate tab keypresses!
200
- // Instead, we depend on the implementation detail that _which_ node you
201
- // exit from determines where you'll end up.)
202
- button1.focus();
203
- expect(document.activeElement).toBe(button1);
204
- button2.focus();
205
- expect(document.activeElement).toBe(button2);
206
- button3.focus();
207
- expect(document.activeElement).toBe(button3);
208
- buttonZ.focus();
209
- expect(document.activeElement).toBe(button1);
210
-
211
- // Then, go backward. Confirm that, when we get to button A, we wrap
212
- // back to button 3.
213
- button3.focus();
214
- expect(document.activeElement).toBe(button3);
215
- button2.focus();
216
- expect(document.activeElement).toBe(button2);
217
- button1.focus();
218
- expect(document.activeElement).toBe(button1);
219
- buttonA.focus();
220
- expect(document.activeElement).toBe(button3);
221
- });
222
206
  });
@@ -5,7 +5,10 @@ import {mount, shallow} from "enzyme";
5
5
  import ModalLauncher from "../modal-launcher.js";
6
6
  import OnePaneDialog from "../one-pane-dialog.js";
7
7
 
8
- const sleep = (duration: number = 0) =>
8
+ import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
9
+ import {getElementAttachedToDocument} from "../../../../../utils/testing/get-element-attached-to-document.js";
10
+
11
+ const wait = (duration: number = 0) =>
9
12
  new Promise((resolve, reject) => setTimeout(resolve, duration));
10
13
 
11
14
  const exampleModal = (
@@ -16,19 +19,41 @@ const exampleModal = (
16
19
  );
17
20
 
18
21
  describe("ModalLauncher", () => {
22
+ beforeEach(() => {
23
+ jest.useRealTimers();
24
+ });
25
+
26
+ afterEach(() => {
27
+ unmountAll();
28
+ if (document.body) {
29
+ document.body.innerHTML = "";
30
+ }
31
+ });
32
+
19
33
  window.scrollTo = jest.fn();
20
34
 
21
- test("Children can launch the modal", () => {
35
+ test("Children can launch the modal", async () => {
36
+ // Arrange
37
+ // We need the elements in the DOM document, it seems, for this test
38
+ // to work. Changing to testing-library will likely fix this.
39
+ const containerDiv = getElementAttachedToDocument("container");
22
40
  const wrapper = mount(
23
41
  <ModalLauncher modal={exampleModal}>
24
42
  {({openModal}) => <button onClick={openModal} />}
25
43
  </ModalLauncher>,
44
+ {attachTo: containerDiv},
26
45
  );
46
+
47
+ // Act
27
48
  wrapper.find("button").simulate("click");
49
+ await wait();
50
+
28
51
  const portal = global.document.querySelector(
29
52
  "[data-modal-launcher-portal]",
30
53
  );
31
- expect(portal instanceof HTMLDivElement).toBe(true);
54
+
55
+ // Assert
56
+ expect(portal).toBeInstanceOf(HTMLDivElement);
32
57
  });
33
58
 
34
59
  test("Modal can be manually opened and closed", () => {
@@ -62,7 +87,7 @@ describe("ModalLauncher", () => {
62
87
  // this function receives a `closeModal` argument that works.
63
88
  const modalFn = ({closeModal}: {|closeModal: () => void|}) => {
64
89
  expect(opened).toBe(true);
65
- setImmediate(closeModal);
90
+ setTimeout(closeModal, 0);
66
91
  return exampleModal;
67
92
  };
68
93
 
@@ -110,6 +135,7 @@ describe("ModalLauncher", () => {
110
135
 
111
136
  // Simulate an Escape keypress.
112
137
  const event: KeyboardEvent = (document.createEvent("Event"): any);
138
+ // $FlowIgnore[cannot-write]
113
139
  event.key = "Escape";
114
140
  event.initEvent("keyup", true, true);
115
141
  document.dispatchEvent(event);
@@ -254,7 +280,7 @@ describe("ModalLauncher", () => {
254
280
  wrapper.find("button").simulate("click");
255
281
 
256
282
  // wait for styles to be applied
257
- await sleep();
283
+ await wait();
258
284
 
259
285
  // Assert
260
286
  expect(document.activeElement).not.toBe(lastButton);
@@ -262,6 +288,9 @@ describe("ModalLauncher", () => {
262
288
 
263
289
  test("if modal is closed, return focus to the last element focused outside the modal", async () => {
264
290
  // Arrange
291
+ // We need the elements in the DOM document, it seems, for this test
292
+ // to work. Changing to testing-library will likely fix this.
293
+ const containerDiv = getElementAttachedToDocument("container");
265
294
  let savedCloseModal = () => {
266
295
  throw new Error(`closeModal wasn't saved`);
267
296
  };
@@ -277,6 +306,7 @@ describe("ModalLauncher", () => {
277
306
  <button onClick={openModal} data-last-focused-button />
278
307
  )}
279
308
  </ModalLauncher>,
309
+ {attachTo: containerDiv},
280
310
  );
281
311
 
282
312
  const lastButton = wrapper
@@ -289,7 +319,7 @@ describe("ModalLauncher", () => {
289
319
  wrapper.find("button").simulate("click");
290
320
 
291
321
  // wait for styles to be applied
292
- await sleep();
322
+ await wait();
293
323
 
294
324
  // Act
295
325
  savedCloseModal(); // close the modal
@@ -7,6 +7,8 @@ import Color from "@khanacademy/wonder-blocks-color";
7
7
  import {View} from "@khanacademy/wonder-blocks-core";
8
8
  import {ModalLauncherPortalAttributeName} from "../util/constants.js";
9
9
 
10
+ import {findFocusableNodes} from "../util/find-focusable-nodes.js";
11
+
10
12
  import type {ModalElement} from "../util/types.js";
11
13
 
12
14
  type Props = {|
@@ -24,13 +26,6 @@ type Props = {|
24
26
  testId?: string,
25
27
  |};
26
28
 
27
- /**
28
- * List of elements that can be focused
29
- * @see https://www.w3.org/TR/html5/editing.html#can-be-focused
30
- */
31
- const FOCUSABLE_ELEMENTS =
32
- 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
33
-
34
29
  /**
35
30
  * A private component used by ModalLauncher. This is the fixed-position
36
31
  * container element that gets mounted outside the DOM. It overlays the modal
@@ -82,7 +77,7 @@ export default class ModalBackdrop extends React.Component<Props> {
82
77
  */
83
78
  _getFirstFocusableElement(node: HTMLElement): HTMLElement | null {
84
79
  // get a collection of elements that can be focused
85
- const focusableElements = node.querySelectorAll(FOCUSABLE_ELEMENTS);
80
+ const focusableElements = findFocusableNodes(node);
86
81
 
87
82
  if (!focusableElements) {
88
83
  return null;
@@ -38,7 +38,7 @@ const customViewports = {
38
38
  };
39
39
 
40
40
  export default {
41
- title: "OnePaneDialog",
41
+ title: "Floating/Modal/OnePaneDialog",
42
42
  parameters: {
43
43
  viewport: {
44
44
  viewports: customViewports,
@@ -191,14 +191,12 @@ export const withOpener: StoryComponentType = () => {
191
191
  );
192
192
  };
193
193
 
194
- withOpener.story = {
195
- parameters: {
196
- viewport: {
197
- defaultViewport: null,
198
- },
199
- chromatic: {
200
- // Don't take screenshots of this story since it would only show a button.
201
- disable: true,
202
- },
194
+ withOpener.parameters = {
195
+ viewport: {
196
+ defaultViewport: null,
197
+ },
198
+ chromatic: {
199
+ // Don't take screenshots of this story since it would only show a button.
200
+ disableSnapshot: true,
203
201
  },
204
202
  };
@@ -0,0 +1,14 @@
1
+ // @flow
2
+
3
+ /**
4
+ * List of elements that can be focused
5
+ * @see https://www.w3.org/TR/html5/editing.html#can-be-focused
6
+ */
7
+ const FOCUSABLE_ELEMENTS =
8
+ 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
9
+
10
+ export function findFocusableNodes(
11
+ root: HTMLElement | Document,
12
+ ): Array<HTMLElement> {
13
+ return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
14
+ }
@@ -108,9 +108,8 @@ describe("maybeGetPortalMountedModalHostElement", () => {
108
108
  if (node) {
109
109
  // Act
110
110
  const candidateElement = ReactDOM.findDOMNode(node);
111
- const result = maybeGetPortalMountedModalHostElement(
112
- candidateElement,
113
- );
111
+ const result =
112
+ maybeGetPortalMountedModalHostElement(candidateElement);
114
113
 
115
114
  // Assert
116
115
  expect(result).toBeTruthy();
@@ -1,23 +0,0 @@
1
- // @flow
2
- describe("@khanacademy/wonder-blocks-modal", () => {
3
- test("package exports default", async () => {
4
- // Arrange
5
- const importedModule = import("../index.js");
6
-
7
- // Act
8
- const result = await importedModule;
9
-
10
- // Assert
11
- expect(Object.keys(result).sort()).toEqual(
12
- [
13
- "ModalDialog",
14
- "ModalFooter",
15
- "ModalHeader",
16
- "ModalPanel",
17
- "ModalLauncher",
18
- "OnePaneDialog",
19
- "maybeGetPortalMountedModalHostElement",
20
- ].sort(),
21
- );
22
- });
23
- });