@khanacademy/wonder-blocks-modal 2.1.43 → 2.2.1
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/CHANGELOG.md +23 -0
- package/dist/es/index.js +96 -85
- package/dist/index.js +42 -23
- package/package.json +15 -16
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +11 -11
- package/src/components/__tests__/close-button.test.js +1 -0
- package/src/components/__tests__/modal-backdrop.test.js +115 -57
- package/src/components/__tests__/modal-header.test.js +1 -0
- package/src/components/__tests__/modal-launcher.test.js +37 -6
- package/src/components/__tests__/modal-panel.test.js +1 -0
- package/src/components/__tests__/one-pane-dialog.test.js +1 -0
- package/src/components/modal-backdrop.js +17 -13
- package/src/components/one-pane-dialog.stories.js +11 -13
- package/src/util/find-focusable-nodes.js +14 -0
- package/src/util/maybe-get-portal-mounted-modal-host-element.test.js +3 -3
- package/LICENSE +0 -21
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 =
|
|
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__(
|
|
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__(
|
|
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.
|
|
1294
|
-
//
|
|
1295
|
-
//
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/*
|
|
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__(
|
|
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__(
|
|
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
|
-
/*
|
|
1574
|
+
/* 23 */
|
|
1556
1575
|
/***/ (function(module, exports) {
|
|
1557
1576
|
|
|
1558
1577
|
module.exports = require("@khanacademy/wonder-blocks-icon");
|
|
1559
1578
|
|
|
1560
1579
|
/***/ }),
|
|
1561
|
-
/*
|
|
1580
|
+
/* 24 */
|
|
1562
1581
|
/***/ (function(module, exports) {
|
|
1563
1582
|
|
|
1564
1583
|
module.exports = require("@khanacademy/wonder-blocks-icon-button");
|
|
1565
1584
|
|
|
1566
1585
|
/***/ }),
|
|
1567
|
-
/*
|
|
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
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"design": "v2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -15,24 +15,23 @@
|
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@babel/runtime": "^7.
|
|
19
|
-
"@khanacademy/wonder-blocks-breadcrumbs": "^1.0.
|
|
20
|
-
"@khanacademy/wonder-blocks-color": "^1.1.
|
|
21
|
-
"@khanacademy/wonder-blocks-core": "^
|
|
22
|
-
"@khanacademy/wonder-blocks-icon": "^1.2.
|
|
23
|
-
"@khanacademy/wonder-blocks-icon-button": "^3.3
|
|
24
|
-
"@khanacademy/wonder-blocks-layout": "^1.4.
|
|
25
|
-
"@khanacademy/wonder-blocks-spacing": "^3.0.
|
|
26
|
-
"@khanacademy/wonder-blocks-toolbar": "^2.1.
|
|
27
|
-
"@khanacademy/wonder-blocks-typography": "^1.1.
|
|
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.3",
|
|
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",
|
|
31
|
-
"react": "
|
|
32
|
-
"react-dom": "
|
|
31
|
+
"react": "16.14.0",
|
|
32
|
+
"react-dom": "16.14.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"wb-dev-build-settings": "^0.
|
|
36
|
-
}
|
|
37
|
-
"gitHead": "9a9cc04bf2bbfb425f991a347b4f8b0d0d56e120"
|
|
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,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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
65
|
+
const backdrop = screen.getByTestId("modal-backdrop-test-id");
|
|
66
|
+
|
|
67
|
+
//Act
|
|
68
|
+
userEvent.click(backdrop);
|
|
46
69
|
|
|
47
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
});
|