@khanacademy/wonder-blocks-modal 2.1.44 → 2.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.
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ module.exports =
82
82
  /******/
83
83
  /******/
84
84
  /******/ // Load entry module and return exports
85
- /******/ return __webpack_require__(__webpack_require__.s = 24);
85
+ /******/ return __webpack_require__(__webpack_require__.s = 25);
86
86
  /******/ })
87
87
  /************************************************************************/
88
88
  /******/ ([
@@ -477,7 +477,7 @@ const styleSheets = {
477
477
  /* harmony import */ var _modal_content_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(14);
478
478
  /* harmony import */ var _modal_header_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7);
479
479
  /* harmony import */ var _modal_footer_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(8);
480
- /* harmony import */ var _close_button_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(21);
480
+ /* harmony import */ var _close_button_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(22);
481
481
 
482
482
 
483
483
 
@@ -711,7 +711,7 @@ const styleSheets = {
711
711
  /* harmony import */ var aphrodite__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(aphrodite__WEBPACK_IMPORTED_MODULE_2__);
712
712
  /* harmony import */ var _focus_trap_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(18);
713
713
  /* harmony import */ var _modal_backdrop_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(19);
714
- /* harmony import */ var _scroll_disabler_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(20);
714
+ /* harmony import */ var _scroll_disabler_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(21);
715
715
  /* harmony import */ var _modal_context_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(12);
716
716
 
717
717
 
@@ -1261,6 +1261,7 @@ class FocusTrap extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1261
1261
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2);
1262
1262
  /* harmony import */ var _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__);
1263
1263
  /* harmony import */ var _util_constants_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(11);
1264
+ /* harmony import */ var _util_find_focusable_nodes_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(20);
1264
1265
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
1265
1266
 
1266
1267
 
@@ -1270,11 +1271,7 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
1270
1271
 
1271
1272
 
1272
1273
 
1273
- /**
1274
- * List of elements that can be focused
1275
- * @see https://www.w3.org/TR/html5/editing.html#can-be-focused
1276
- */
1277
- const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
1274
+
1278
1275
  /**
1279
1276
  * A private component used by ModalLauncher. This is the fixed-position
1280
1277
  * container element that gets mounted outside the DOM. It overlays the modal
@@ -1285,17 +1282,24 @@ const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:no
1285
1282
  * and adding an `onClose` prop that will call `onCloseModal`. If an
1286
1283
  * `onClose` prop is already provided, the two are merged.
1287
1284
  */
1288
-
1289
1285
  class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1290
1286
  constructor(...args) {
1291
1287
  super(...args);
1288
+ this._mousePressedOutside = false;
1289
+
1290
+ this.handleMouseDown = e => {
1291
+ // Confirm that it is the backdrop that is being clicked, not the child
1292
+ this._mousePressedOutside = e.target === e.currentTarget;
1293
+ };
1292
1294
 
1293
- this.handleClick = e => {
1294
- // Was the lowest-level click target (`e.target`) the positioner element
1295
- // (`e.currentTarget`)?
1296
- if (e.target === e.currentTarget) {
1295
+ this.handleMouseUp = e => {
1296
+ // Confirm that it is the backdrop that is being clicked, not the child
1297
+ // and that the mouse was pressed in the backdrop first.
1298
+ if (e.target === e.currentTarget && this._mousePressedOutside) {
1297
1299
  this.props.onCloseModal();
1298
1300
  }
1301
+
1302
+ this._mousePressedOutside = false;
1299
1303
  };
1300
1304
  }
1301
1305
 
@@ -1316,11 +1320,10 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1316
1320
  firstFocusableElement.focus();
1317
1321
  }, 0);
1318
1322
  }
1323
+
1319
1324
  /**
1320
1325
  * Returns an element specified by the user
1321
1326
  */
1322
-
1323
-
1324
1327
  _getInitialFocusElement(node) {
1325
1328
  const {
1326
1329
  initialFocusId
@@ -1339,7 +1342,7 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1339
1342
 
1340
1343
  _getFirstFocusableElement(node) {
1341
1344
  // get a collection of elements that can be focused
1342
- const focusableElements = node.querySelectorAll(FOCUSABLE_ELEMENTS);
1345
+ const focusableElements = Object(_util_find_focusable_nodes_js__WEBPACK_IMPORTED_MODULE_6__[/* findFocusableNodes */ "a"])(node);
1343
1346
 
1344
1347
  if (!focusableElements) {
1345
1348
  return null;
@@ -1378,7 +1381,8 @@ class ModalBackdrop extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1378
1381
  };
1379
1382
  return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](_khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_4__["View"], _extends({
1380
1383
  style: styles.modalPositioner,
1381
- onClick: this.handleClick,
1384
+ onMouseDown: this.handleMouseDown,
1385
+ onMouseUp: this.handleMouseUp,
1382
1386
  testId: testId
1383
1387
  }, backdropProps), children);
1384
1388
  }
@@ -1410,6 +1414,21 @@ const styles = aphrodite__WEBPACK_IMPORTED_MODULE_2__["StyleSheet"].create({
1410
1414
  /* 20 */
1411
1415
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1412
1416
 
1417
+ "use strict";
1418
+ /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return findFocusableNodes; });
1419
+ /**
1420
+ * List of elements that can be focused
1421
+ * @see https://www.w3.org/TR/html5/editing.html#can-be-focused
1422
+ */
1423
+ const FOCUSABLE_ELEMENTS = 'a[href], details, input, textarea, select, button:not([aria-label^="Close"])';
1424
+ function findFocusableNodes(root) {
1425
+ return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS));
1426
+ }
1427
+
1428
+ /***/ }),
1429
+ /* 21 */
1430
+ /***/ (function(module, __webpack_exports__, __webpack_require__) {
1431
+
1413
1432
  "use strict";
1414
1433
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1415
1434
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
@@ -1504,16 +1523,16 @@ ScrollDisabler.numModalsOpened = 0;
1504
1523
  /* harmony default export */ __webpack_exports__["a"] = (ScrollDisabler);
1505
1524
 
1506
1525
  /***/ }),
1507
- /* 21 */
1526
+ /* 22 */
1508
1527
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1509
1528
 
1510
1529
  "use strict";
1511
1530
  /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return CloseButton; });
1512
1531
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
1513
1532
  /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
1514
- /* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(22);
1533
+ /* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(23);
1515
1534
  /* harmony import */ var _khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_icon__WEBPACK_IMPORTED_MODULE_1__);
1516
- /* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(23);
1535
+ /* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(24);
1517
1536
  /* harmony import */ var _khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_blocks_icon_button__WEBPACK_IMPORTED_MODULE_2__);
1518
1537
  /* harmony import */ var _modal_context_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12);
1519
1538
 
@@ -1552,19 +1571,19 @@ class CloseButton extends react__WEBPACK_IMPORTED_MODULE_0__["Component"] {
1552
1571
  }
1553
1572
 
1554
1573
  /***/ }),
1555
- /* 22 */
1574
+ /* 23 */
1556
1575
  /***/ (function(module, exports) {
1557
1576
 
1558
1577
  module.exports = require("@khanacademy/wonder-blocks-icon");
1559
1578
 
1560
1579
  /***/ }),
1561
- /* 23 */
1580
+ /* 24 */
1562
1581
  /***/ (function(module, exports) {
1563
1582
 
1564
1583
  module.exports = require("@khanacademy/wonder-blocks-icon-button");
1565
1584
 
1566
1585
  /***/ }),
1567
- /* 24 */
1586
+ /* 25 */
1568
1587
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
1569
1588
 
1570
1589
  "use strict";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-modal",
3
- "version": "2.1.44",
3
+ "version": "2.2.2",
4
4
  "design": "v2",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -15,16 +15,16 @@
15
15
  "author": "",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@babel/runtime": "^7.13.10",
19
- "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.26",
20
- "@khanacademy/wonder-blocks-color": "^1.1.19",
21
- "@khanacademy/wonder-blocks-core": "^3.2.0",
22
- "@khanacademy/wonder-blocks-icon": "^1.2.23",
23
- "@khanacademy/wonder-blocks-icon-button": "^3.4.0",
24
- "@khanacademy/wonder-blocks-layout": "^1.4.5",
25
- "@khanacademy/wonder-blocks-spacing": "^3.0.4",
26
- "@khanacademy/wonder-blocks-toolbar": "^2.1.27",
27
- "@khanacademy/wonder-blocks-typography": "^1.1.27"
18
+ "@babel/runtime": "^7.16.3",
19
+ "@khanacademy/wonder-blocks-breadcrumbs": "^1.0.28",
20
+ "@khanacademy/wonder-blocks-color": "^1.1.20",
21
+ "@khanacademy/wonder-blocks-core": "^4.2.1",
22
+ "@khanacademy/wonder-blocks-icon": "^1.2.25",
23
+ "@khanacademy/wonder-blocks-icon-button": "^3.4.4",
24
+ "@khanacademy/wonder-blocks-layout": "^1.4.7",
25
+ "@khanacademy/wonder-blocks-spacing": "^3.0.5",
26
+ "@khanacademy/wonder-blocks-toolbar": "^2.1.29",
27
+ "@khanacademy/wonder-blocks-typography": "^1.1.29"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "aphrodite": "^1.2.5",
@@ -32,7 +32,6 @@
32
32
  "react-dom": "16.14.0"
33
33
  },
34
34
  "devDependencies": {
35
- "wb-dev-build-settings": "^0.1.2"
36
- },
37
- "gitHead": "b6193f70c73e70fbaf76bc688dc69a47fb1d0ef3"
35
+ "wb-dev-build-settings": "^0.3.0"
36
+ }
38
37
  }
@@ -21,8 +21,8 @@ exports[`wonder-blocks-modal example 1 1`] = `
21
21
  }
22
22
  >
23
23
  <button
24
+ aria-disabled={false}
24
25
  className=""
25
- disabled={false}
26
26
  onBlur={[Function]}
27
27
  onClick={[Function]}
28
28
  onDragStart={[Function]}
@@ -116,8 +116,8 @@ exports[`wonder-blocks-modal example 2 1`] = `
116
116
  }
117
117
  >
118
118
  <button
119
+ aria-disabled={false}
119
120
  className=""
120
- disabled={false}
121
121
  onBlur={[Function]}
122
122
  onClick={[Function]}
123
123
  onDragStart={[Function]}
@@ -392,8 +392,8 @@ exports[`wonder-blocks-modal example 4 1`] = `
392
392
  }
393
393
  >
394
394
  <button
395
+ aria-disabled={false}
395
396
  className=""
396
- disabled={false}
397
397
  onBlur={[Function]}
398
398
  onClick={[Function]}
399
399
  onDragStart={[Function]}
@@ -487,8 +487,8 @@ exports[`wonder-blocks-modal example 5 1`] = `
487
487
  }
488
488
  >
489
489
  <button
490
+ aria-disabled={false}
490
491
  className=""
491
- disabled={false}
492
492
  onBlur={[Function]}
493
493
  onClick={[Function]}
494
494
  onDragStart={[Function]}
@@ -1387,8 +1387,8 @@ exports[`wonder-blocks-modal example 7 1`] = `
1387
1387
  }
1388
1388
  >
1389
1389
  <button
1390
+ aria-disabled={false}
1390
1391
  className=""
1391
- disabled={false}
1392
1392
  onBlur={[Function]}
1393
1393
  onClick={[Function]}
1394
1394
  onDragStart={[Function]}
@@ -1996,8 +1996,8 @@ exports[`wonder-blocks-modal example 8 1`] = `
1996
1996
  }
1997
1997
  >
1998
1998
  <button
1999
+ aria-disabled={false}
1999
2000
  className=""
2000
- disabled={false}
2001
2001
  onBlur={[Function]}
2002
2002
  onClick={[Function]}
2003
2003
  onDragStart={[Function]}
@@ -2070,8 +2070,8 @@ exports[`wonder-blocks-modal example 8 1`] = `
2070
2070
  </span>
2071
2071
  </button>
2072
2072
  <button
2073
+ aria-disabled={false}
2073
2074
  className=""
2074
- disabled={false}
2075
2075
  onBlur={[Function]}
2076
2076
  onClick={[Function]}
2077
2077
  onDragStart={[Function]}
@@ -2144,8 +2144,8 @@ exports[`wonder-blocks-modal example 8 1`] = `
2144
2144
  </span>
2145
2145
  </button>
2146
2146
  <button
2147
+ aria-disabled={false}
2147
2148
  className=""
2148
- disabled={false}
2149
2149
  onBlur={[Function]}
2150
2150
  onClick={[Function]}
2151
2151
  onDragStart={[Function]}
@@ -2627,8 +2627,8 @@ exports[`wonder-blocks-modal example 9 1`] = `
2627
2627
  }
2628
2628
  >
2629
2629
  <button
2630
+ aria-disabled={false}
2630
2631
  className=""
2631
- disabled={false}
2632
2632
  onBlur={[Function]}
2633
2633
  onClick={[Function]}
2634
2634
  onDragStart={[Function]}
@@ -2726,8 +2726,8 @@ exports[`wonder-blocks-modal example 9 1`] = `
2726
2726
  }
2727
2727
  />
2728
2728
  <button
2729
+ aria-disabled={false}
2729
2730
  className=""
2730
- disabled={false}
2731
2731
  onBlur={[Function]}
2732
2732
  onClick={[Function]}
2733
2733
  onDragStart={[Function]}
@@ -3321,8 +3321,8 @@ exports[`wonder-blocks-modal example 10 1`] = `
3321
3321
  }
3322
3322
  />
3323
3323
  <button
3324
+ aria-disabled={false}
3324
3325
  className=""
3325
- disabled={false}
3326
3326
  onBlur={[Function]}
3327
3327
  onClick={[Function]}
3328
3328
  onDragStart={[Function]}
@@ -1,6 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {mount} from "enzyme";
4
+ import "jest-enzyme";
4
5
 
5
6
  import expectRenderError from "../../../../../utils/testing/expect-render-error.js";
6
7
  import CloseButton from "../close-button.js";
@@ -1,11 +1,17 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {mount} from "enzyme";
4
+ import "jest-enzyme";
5
+ import {render, screen, fireEvent} from "@testing-library/react";
6
+ import userEvent from "@testing-library/user-event";
4
7
 
5
8
  import ModalBackdrop from "../modal-backdrop.js";
6
9
  import OnePaneDialog from "../one-pane-dialog.js";
7
10
 
8
- const sleep = (duration: number = 0) =>
11
+ import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
12
+ import {getElementAttachedToDocument} from "../../../../../utils/testing/get-element-attached-to-document.js";
13
+
14
+ const wait = (duration: number = 0) =>
9
15
  new Promise((resolve, reject) => setTimeout(resolve, duration));
10
16
 
11
17
  const exampleModal = (
@@ -13,6 +19,7 @@ const exampleModal = (
13
19
  content={<div data-modal-content />}
14
20
  title="Title"
15
21
  footer={<div data-modal-footer />}
22
+ testId="example-modal-test-id"
16
23
  />
17
24
  );
18
25
 
@@ -31,20 +38,36 @@ const exampleModalWithButtons = (
31
38
  );
32
39
 
33
40
  describe("ModalBackdrop", () => {
41
+ beforeEach(() => {
42
+ jest.useRealTimers();
43
+ });
44
+
45
+ afterEach(() => {
46
+ unmountAll();
47
+ if (document.body) {
48
+ document.body.innerHTML = "";
49
+ }
50
+ });
51
+
34
52
  test("Clicking the backdrop triggers `onCloseModal`", () => {
53
+ // Arrange
35
54
  const onCloseModal = jest.fn();
36
55
 
37
- // We use `mount` instead of `shallow` here, because the component's
38
- // click handler expects actual DOM events.
39
- const wrapper = mount(
40
- <ModalBackdrop onCloseModal={onCloseModal}>
56
+ render(
57
+ <ModalBackdrop
58
+ onCloseModal={onCloseModal}
59
+ testId="modal-backdrop-test-id"
60
+ >
41
61
  {exampleModal}
42
62
  </ModalBackdrop>,
43
63
  );
44
64
 
45
- expect(onCloseModal).not.toHaveBeenCalled();
65
+ const backdrop = screen.getByTestId("modal-backdrop-test-id");
66
+
67
+ //Act
68
+ userEvent.click(backdrop);
46
69
 
47
- wrapper.simulate("click");
70
+ // Assert
48
71
  expect(onCloseModal).toHaveBeenCalled();
49
72
  });
50
73
 
@@ -63,6 +86,62 @@ describe("ModalBackdrop", () => {
63
86
  expect(onCloseModal).not.toHaveBeenCalled();
64
87
  });
65
88
 
89
+ test("Clicking and dragging into the backdrop does not close modal", () => {
90
+ // Arrange
91
+ const onCloseModal = jest.fn();
92
+
93
+ render(
94
+ <ModalBackdrop
95
+ onCloseModal={onCloseModal}
96
+ testId="modal-backdrop-test-id"
97
+ >
98
+ {exampleModal}
99
+ </ModalBackdrop>,
100
+ );
101
+
102
+ const panel = screen.getByTestId("example-modal-test-id");
103
+ const backdrop = screen.getByTestId("modal-backdrop-test-id");
104
+
105
+ // Act
106
+
107
+ // Dragging the mouse
108
+ // eslint-disable-next-line testing-library/prefer-user-event
109
+ fireEvent.mouseDown(panel);
110
+ // eslint-disable-next-line testing-library/prefer-user-event
111
+ fireEvent.mouseUp(backdrop);
112
+
113
+ // Assert
114
+ expect(onCloseModal).not.toHaveBeenCalled();
115
+ });
116
+
117
+ test("Clicking and dragging in from the backdrop does not close modal", () => {
118
+ // Arrange
119
+ const onCloseModal = jest.fn();
120
+
121
+ render(
122
+ <ModalBackdrop
123
+ onCloseModal={onCloseModal}
124
+ testId="modal-backdrop-test-id"
125
+ >
126
+ {exampleModal}
127
+ </ModalBackdrop>,
128
+ );
129
+
130
+ const panel = screen.getByTestId("example-modal-test-id");
131
+ const backdrop = screen.getByTestId("modal-backdrop-test-id");
132
+
133
+ // Act
134
+
135
+ // Dragging the mouse
136
+ // eslint-disable-next-line testing-library/prefer-user-event
137
+ fireEvent.mouseDown(backdrop);
138
+ // eslint-disable-next-line testing-library/prefer-user-event
139
+ fireEvent.mouseUp(panel);
140
+
141
+ // Assert
142
+ expect(onCloseModal).not.toHaveBeenCalled();
143
+ });
144
+
66
145
  test("Clicking the modal footer does not trigger `onCloseModal`", () => {
67
146
  const onCloseModal = jest.fn();
68
147
 
@@ -80,6 +159,10 @@ describe("ModalBackdrop", () => {
80
159
 
81
160
  test("If initialFocusId is set and element is found, we focus that element inside the modal", async () => {
82
161
  // Arrange
162
+ // We need the elements in the DOM document, it seems, for this test
163
+ // to work. Changing to testing-library will likely fix this.
164
+ // Then we can remove the lint suppression.
165
+ const attachElement = getElementAttachedToDocument("container");
83
166
  const initialFocusId = "initial-focus";
84
167
 
85
168
  const wrapper = mount(
@@ -98,21 +181,28 @@ describe("ModalBackdrop", () => {
98
181
  footer={<div data-modal-footer />}
99
182
  />
100
183
  </ModalBackdrop>,
184
+ {attachTo: attachElement},
101
185
  );
102
186
 
103
187
  // Act
104
- await sleep(); // wait for styles to be applied
188
+ await wait(); // wait for styles to be applied
105
189
  const initialFocusElement = wrapper.find(`#${initialFocusId}`);
106
190
 
107
191
  // Assert
108
192
  // first we verify the element exists in the DOM
109
193
  expect(initialFocusElement).toHaveLength(1);
194
+
110
195
  // verify the focus is set on the correct element
196
+ // eslint-disable-next-line testing-library/no-node-access
111
197
  expect(document.activeElement).toBe(initialFocusElement.getDOMNode());
112
198
  });
113
199
 
114
200
  test("If initialFocusId is set but element is NOT found, we focus on the first focusable element instead", async () => {
115
201
  // Arrange
202
+ // We need the elements in the DOM document, it seems, for this test
203
+ // to work. Changing to testing-library will likely fix this.
204
+ // Then we can remove the lint suppression.
205
+ const attachElement = getElementAttachedToDocument("container");
116
206
  const initialFocusId = "initial-focus";
117
207
  const firstFocusableElement = "[data-first-button]";
118
208
 
@@ -123,16 +213,18 @@ describe("ModalBackdrop", () => {
123
213
  >
124
214
  {exampleModalWithButtons}
125
215
  </ModalBackdrop>,
216
+ {attachTo: attachElement},
126
217
  );
127
218
 
128
219
  // Act
129
- await sleep(); // wait for styles to be applied
220
+ await wait(); // wait for styles to be applied
130
221
  const initialFocusElement = wrapper.find(`#${initialFocusId}`);
131
222
 
132
223
  // Assert
133
224
  // first we verify the element doesn't exist in the DOM
134
225
  expect(initialFocusElement).toHaveLength(0);
135
226
  // verify the focus is set on the first focusable element instead
227
+ // eslint-disable-next-line testing-library/no-node-access
136
228
  expect(document.activeElement).toBe(
137
229
  wrapper.find(firstFocusableElement).getDOMNode(),
138
230
  );
@@ -140,83 +232,49 @@ describe("ModalBackdrop", () => {
140
232
 
141
233
  test("If no initialFocusId is set, we focus the first button in the modal", async () => {
142
234
  // Arrange
235
+ // We need the elements in the DOM document, it seems, for this test
236
+ // to work. Changing to testing-library will likely fix this.
237
+ // Then we can remove the lint suppression.
238
+ const attachElement = getElementAttachedToDocument("container");
143
239
  const wrapper = mount(
144
240
  <ModalBackdrop onCloseModal={() => {}}>
145
241
  {exampleModalWithButtons}
146
242
  </ModalBackdrop>,
243
+ {attachTo: attachElement},
147
244
  );
148
245
 
149
246
  // Act
150
- await sleep(); // wait for styles to be applied
247
+ await wait(); // wait for styles to be applied
151
248
  const focusableElement = wrapper
152
249
  .find("[data-first-button]")
153
250
  .getDOMNode();
154
251
 
155
252
  // Assert
253
+ // eslint-disable-next-line testing-library/no-node-access
156
254
  expect(document.activeElement).toBe(focusableElement);
157
255
  });
158
256
 
159
257
  test("If there are no focusable elements, we focus the Dialog instead", async () => {
160
258
  // Arrange
259
+ // We need the elements in the DOM document, it seems, for this test
260
+ // to work. Changing to testing-library will likely fix this.
261
+ // Then we can remove the lint suppression.
262
+ const attachElement = getElementAttachedToDocument("container");
161
263
  const wrapper = mount(
162
264
  <ModalBackdrop onCloseModal={() => {}}>
163
265
  {exampleModal}
164
266
  </ModalBackdrop>,
267
+ {attachTo: attachElement},
165
268
  );
166
269
 
167
270
  // Act
168
- await sleep(); // wait for styles to be applied
271
+ await wait(); // wait for styles to be applied
169
272
  const focusableElement = wrapper
170
273
  .find('div[role="dialog"]')
171
274
  .getDOMNode();
172
275
 
173
276
  // Assert
277
+ // eslint-disable-next-line testing-library/no-node-access
174
278
  expect(document.activeElement).toBe(focusableElement);
175
279
  });
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
280
  });
@@ -1,6 +1,7 @@
1
1
  // @flow
2
2
  import * as React from "react";
3
3
  import {mount, shallow} from "enzyme";
4
+ import "jest-enzyme";
4
5
 
5
6
  import {
6
7
  Breadcrumbs,