newspack-components 4.6.2 → 4.7.0-alpha.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/colors/colors.module.scss +13 -7
- package/dist/cjs/autocomplete-tokenfield/style.scss +4 -2
- package/dist/cjs/breadcrumbs/format-count.js +33 -0
- package/dist/cjs/breadcrumbs/index.js +78 -20
- package/dist/cjs/breadcrumbs/index.test.js +110 -0
- package/dist/cjs/breadcrumbs/style.scss +6 -0
- package/dist/cjs/button/index.js +41 -17
- package/dist/cjs/button/index.test.js +273 -0
- package/dist/cjs/card/core-card.js +9 -4
- package/dist/cjs/card/core-card.test.js +29 -0
- package/dist/cjs/card/index.js +4 -2
- package/dist/cjs/card/style-core.scss +59 -0
- package/dist/cjs/card-feature/index.js +7 -1
- package/dist/cjs/card-feature/index.test.js +92 -0
- package/dist/cjs/color-picker/index.js +1 -1
- package/dist/cjs/confirm-dialog/index.js +26 -9
- package/dist/cjs/confirm-dialog/index.test.js +176 -0
- package/dist/cjs/dataviews/index.js +11 -2
- package/dist/cjs/divider/index.js +13 -0
- package/dist/cjs/drawer/action.js +74 -0
- package/dist/cjs/drawer/body.js +79 -0
- package/dist/cjs/drawer/close-icon.js +45 -0
- package/dist/cjs/drawer/content.js +72 -0
- package/dist/cjs/drawer/context.js +23 -0
- package/dist/cjs/drawer/context.test.js +43 -0
- package/dist/cjs/drawer/divider.js +24 -0
- package/dist/cjs/drawer/footer.js +26 -0
- package/dist/cjs/drawer/header.js +26 -0
- package/dist/cjs/drawer/index.js +42 -0
- package/dist/cjs/drawer/index.test.js +1287 -0
- package/dist/cjs/drawer/root.js +350 -0
- package/dist/cjs/drawer/slots.test.js +422 -0
- package/dist/cjs/drawer/style.scss +205 -0
- package/dist/cjs/drawer/title.js +54 -0
- package/dist/cjs/drawer/types.js +5 -0
- package/dist/cjs/drawer/use-exit-animation.js +59 -0
- package/dist/cjs/hooks/use-confirm-dialog.js +15 -3
- package/dist/cjs/hooks/use-unsaved-changes-dialog.js +19 -4
- package/dist/cjs/index.js +28 -1
- package/dist/cjs/page/index.js +1 -1
- package/dist/cjs/page-control/index.js +82 -0
- package/dist/cjs/plugin-settings/SettingsSection.js +7 -1
- package/dist/cjs/section-header/index.js +5 -2
- package/dist/cjs/section-header/style.scss +25 -12
- package/dist/cjs/table-card/index.js +67 -0
- package/dist/cjs/table-card/index.test.js +153 -0
- package/dist/cjs/with-wizard-screen/style.scss +8 -0
- package/dist/cjs/wizard/index.js +102 -38
- package/dist/esm/autocomplete-tokenfield/style.scss +4 -2
- package/dist/esm/breadcrumbs/format-count.js +27 -0
- package/dist/esm/breadcrumbs/index.js +81 -20
- package/dist/esm/breadcrumbs/index.test.js +110 -0
- package/dist/esm/breadcrumbs/style.scss +6 -0
- package/dist/esm/button/index.js +42 -18
- package/dist/esm/button/index.test.js +273 -0
- package/dist/esm/card/core-card.js +9 -4
- package/dist/esm/card/core-card.test.js +29 -0
- package/dist/esm/card/index.js +4 -2
- package/dist/esm/card/style-core.scss +59 -0
- package/dist/esm/card-feature/index.js +8 -2
- package/dist/esm/card-feature/index.test.js +96 -0
- package/dist/esm/color-picker/index.js +1 -1
- package/dist/esm/confirm-dialog/index.js +26 -9
- package/dist/esm/confirm-dialog/index.test.js +176 -0
- package/dist/esm/dataviews/index.js +11 -2
- package/dist/esm/divider/index.js +14 -0
- package/dist/esm/drawer/action.js +68 -0
- package/dist/esm/drawer/body.js +70 -0
- package/dist/esm/drawer/close-icon.js +37 -0
- package/dist/esm/drawer/content.js +63 -0
- package/dist/esm/drawer/context.js +17 -0
- package/dist/esm/drawer/context.test.js +40 -0
- package/dist/esm/drawer/divider.js +16 -0
- package/dist/esm/drawer/footer.js +18 -0
- package/dist/esm/drawer/header.js +18 -0
- package/dist/esm/drawer/index.js +35 -0
- package/dist/esm/drawer/index.test.js +1285 -0
- package/dist/esm/drawer/root.js +342 -0
- package/dist/esm/drawer/slots.test.js +422 -0
- package/dist/esm/drawer/style.scss +205 -0
- package/dist/esm/drawer/title.js +46 -0
- package/dist/esm/drawer/types.js +1 -0
- package/dist/esm/drawer/use-exit-animation.js +52 -0
- package/dist/esm/hooks/use-confirm-dialog.js +15 -3
- package/dist/esm/hooks/use-unsaved-changes-dialog.js +20 -5
- package/dist/esm/index.js +4 -0
- package/dist/esm/page/index.js +1 -1
- package/dist/esm/page-control/index.js +82 -0
- package/dist/esm/plugin-settings/SettingsSection.js +8 -2
- package/dist/esm/section-header/index.js +5 -2
- package/dist/esm/section-header/style.scss +25 -12
- package/dist/esm/table-card/index.js +60 -0
- package/dist/esm/table-card/index.test.js +150 -0
- package/dist/esm/with-wizard-screen/style.scss +8 -0
- package/dist/esm/wizard/index.js +104 -38
- package/package.json +2 -2
- package/src/autocomplete-tokenfield/style.scss +4 -2
- package/src/breadcrumbs/format-count.js +27 -0
- package/src/breadcrumbs/index.js +68 -8
- package/src/breadcrumbs/index.test.js +57 -0
- package/src/breadcrumbs/style.scss +6 -0
- package/src/button/index.test.js +143 -0
- package/src/button/index.tsx +32 -7
- package/src/card/core-card.js +15 -2
- package/src/card/core-card.test.js +19 -0
- package/src/card/index.js +3 -0
- package/src/card/style-core.scss +59 -0
- package/src/card-feature/README.md +4 -0
- package/src/card-feature/index.test.js +65 -0
- package/src/card-feature/index.tsx +13 -2
- package/src/color-picker/index.js +1 -1
- package/src/confirm-dialog/README.md +3 -2
- package/src/confirm-dialog/index.test.js +146 -0
- package/src/confirm-dialog/index.tsx +23 -9
- package/src/dataviews/index.tsx +11 -2
- package/src/divider/index.js +13 -0
- package/src/drawer/README.md +454 -0
- package/src/drawer/action.tsx +67 -0
- package/src/drawer/body.tsx +60 -0
- package/src/drawer/close-icon.tsx +37 -0
- package/src/drawer/content.tsx +54 -0
- package/src/drawer/context.test.js +31 -0
- package/src/drawer/context.ts +25 -0
- package/src/drawer/divider.tsx +13 -0
- package/src/drawer/footer.tsx +15 -0
- package/src/drawer/header.tsx +15 -0
- package/src/drawer/index.test.js +1012 -0
- package/src/drawer/index.tsx +33 -0
- package/src/drawer/root.tsx +328 -0
- package/src/drawer/slots.test.js +314 -0
- package/src/drawer/style.scss +205 -0
- package/src/drawer/title.tsx +40 -0
- package/src/drawer/types.ts +79 -0
- package/src/drawer/use-exit-animation.ts +46 -0
- package/src/hooks/use-confirm-dialog.tsx +14 -3
- package/src/hooks/use-unsaved-changes-dialog.tsx +17 -5
- package/src/index.js +4 -0
- package/src/page/index.js +1 -1
- package/src/page-control/index.js +63 -0
- package/src/plugin-settings/SettingsSection.js +8 -2
- package/src/section-header/index.js +5 -2
- package/src/section-header/style.scss +25 -12
- package/src/table-card/README.md +100 -0
- package/src/table-card/index.test.js +112 -0
- package/src/table-card/index.tsx +56 -0
- package/src/with-wizard-screen/style.scss +8 -0
- package/src/wizard/index.js +95 -34
|
@@ -0,0 +1,1285 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
5
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
6
|
+
/**
|
|
7
|
+
* WordPress dependencies
|
|
8
|
+
*/
|
|
9
|
+
import { Modal } from '@wordpress/components';
|
|
10
|
+
import { useReducedMotion } from '@wordpress/compose';
|
|
11
|
+
import { useLayoutEffect, useRef } from '@wordpress/element';
|
|
12
|
+
|
|
13
|
+
// useMediaQuery caches one subscriber per query for the life of the module, so a
|
|
14
|
+
// later matchMedia mock never reaches the hook.
|
|
15
|
+
jest.mock('@wordpress/compose', function () {
|
|
16
|
+
return _objectSpread(_objectSpread({}, jest.requireActual('@wordpress/compose')), {}, {
|
|
17
|
+
useReducedMotion: jest.fn(function () {
|
|
18
|
+
return false;
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
afterEach(function () {
|
|
23
|
+
useReducedMotion.mockReturnValue(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* External dependencies
|
|
28
|
+
*/
|
|
29
|
+
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
30
|
+
import { flushSync } from 'react-dom';
|
|
31
|
+
import { createRoot } from 'react-dom/client';
|
|
32
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Internal dependencies
|
|
36
|
+
*/
|
|
37
|
+
import Drawer from '.';
|
|
38
|
+
import useConfirmDialog from '../hooks/use-confirm-dialog';
|
|
39
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
40
|
+
var noop = function noop() {};
|
|
41
|
+
|
|
42
|
+
// Pass `contents` as an array, not a fragment: Children.toArray flattens arrays only.
|
|
43
|
+
var drawerTree = function drawerTree() {
|
|
44
|
+
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
45
|
+
_ref$rootProps = _ref.rootProps,
|
|
46
|
+
rootProps = _ref$rootProps === void 0 ? {} : _ref$rootProps,
|
|
47
|
+
_ref$header = _ref.header,
|
|
48
|
+
header = _ref$header === void 0 ? true : _ref$header,
|
|
49
|
+
contents = _ref.contents,
|
|
50
|
+
_ref$footer = _ref.footer,
|
|
51
|
+
footer = _ref$footer === void 0 ? null : _ref$footer;
|
|
52
|
+
return /*#__PURE__*/_jsxs(Drawer.Root, _objectSpread(_objectSpread({
|
|
53
|
+
isOpen: true,
|
|
54
|
+
onRequestClose: noop
|
|
55
|
+
}, rootProps), {}, {
|
|
56
|
+
children: [header && /*#__PURE__*/_jsxs(Drawer.Header, {
|
|
57
|
+
children: [/*#__PURE__*/_jsx(Drawer.Title, {
|
|
58
|
+
children: "Drawer title"
|
|
59
|
+
}), /*#__PURE__*/_jsx(Drawer.CloseIcon, {})]
|
|
60
|
+
}), contents || /*#__PURE__*/_jsx(Drawer.Content, {
|
|
61
|
+
children: "Body"
|
|
62
|
+
}), footer]
|
|
63
|
+
}));
|
|
64
|
+
};
|
|
65
|
+
var renderDrawer = function renderDrawer(options) {
|
|
66
|
+
return render(drawerTree(options));
|
|
67
|
+
};
|
|
68
|
+
var panel = function panel() {
|
|
69
|
+
return document.querySelector('.newspack-drawer');
|
|
70
|
+
};
|
|
71
|
+
var overlay = function overlay() {
|
|
72
|
+
return document.querySelector('.newspack-drawer__overlay');
|
|
73
|
+
};
|
|
74
|
+
var closeButton = function closeButton() {
|
|
75
|
+
return screen.getByRole('button', {
|
|
76
|
+
name: 'Close Drawer title'
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
describe('Drawer rendering', function () {
|
|
80
|
+
it('renders nothing when closed', function () {
|
|
81
|
+
renderDrawer({
|
|
82
|
+
rootProps: {
|
|
83
|
+
isOpen: false
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
expect(panel()).not.toBeInTheDocument();
|
|
87
|
+
expect(screen.queryByText('Body')).not.toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
it('renders its children when open', function () {
|
|
90
|
+
renderDrawer();
|
|
91
|
+
expect(screen.getByText('Body')).toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
it('applies the medium size by default', function () {
|
|
94
|
+
renderDrawer();
|
|
95
|
+
expect(panel()).toHaveClass('newspack-drawer', 'newspack-drawer--size-medium');
|
|
96
|
+
});
|
|
97
|
+
it('applies the requested size', function () {
|
|
98
|
+
renderDrawer({
|
|
99
|
+
rootProps: {
|
|
100
|
+
size: 'large'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
expect(panel()).toHaveClass('newspack-drawer--size-large');
|
|
104
|
+
});
|
|
105
|
+
it('falls back to medium and warns on a size it does not know', function () {
|
|
106
|
+
var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
|
|
107
|
+
renderDrawer({
|
|
108
|
+
rootProps: {
|
|
109
|
+
size: 'xlarge'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
expect(panel()).toHaveClass('newspack-drawer--size-medium');
|
|
113
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('unknown size'));
|
|
114
|
+
warn.mockRestore();
|
|
115
|
+
});
|
|
116
|
+
it('takes a ref, a style and a description', function () {
|
|
117
|
+
var ref = {
|
|
118
|
+
current: null
|
|
119
|
+
};
|
|
120
|
+
render(/*#__PURE__*/_jsxs(_Fragment, {
|
|
121
|
+
children: [/*#__PURE__*/_jsx("p", {
|
|
122
|
+
id: "drawer-description",
|
|
123
|
+
children: "What this panel is for"
|
|
124
|
+
}), drawerTree({
|
|
125
|
+
rootProps: {
|
|
126
|
+
ref: ref,
|
|
127
|
+
style: {
|
|
128
|
+
zIndex: 5
|
|
129
|
+
},
|
|
130
|
+
describedBy: 'drawer-description'
|
|
131
|
+
}
|
|
132
|
+
})]
|
|
133
|
+
}));
|
|
134
|
+
expect(ref.current).toBe(overlay());
|
|
135
|
+
expect(panel()).toHaveStyle({
|
|
136
|
+
zIndex: 5
|
|
137
|
+
});
|
|
138
|
+
expect(panel()).toHaveAttribute('aria-describedby', 'drawer-description');
|
|
139
|
+
});
|
|
140
|
+
var twoTitles = function twoTitles() {
|
|
141
|
+
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
142
|
+
_ref2$second = _ref2.second,
|
|
143
|
+
second = _ref2$second === void 0 ? true : _ref2$second,
|
|
144
|
+
_ref2$first = _ref2.first,
|
|
145
|
+
first = _ref2$first === void 0 ? 'Drawer title' : _ref2$first;
|
|
146
|
+
return /*#__PURE__*/_jsxs(Drawer.Root, {
|
|
147
|
+
isOpen: true,
|
|
148
|
+
onRequestClose: noop,
|
|
149
|
+
children: [/*#__PURE__*/_jsxs(Drawer.Header, {
|
|
150
|
+
children: [/*#__PURE__*/_jsx(Drawer.Title, {
|
|
151
|
+
children: first
|
|
152
|
+
}), second && /*#__PURE__*/_jsx(Drawer.Title, {
|
|
153
|
+
children: "Second title"
|
|
154
|
+
})]
|
|
155
|
+
}), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
156
|
+
children: "Body"
|
|
157
|
+
})]
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
var nameOf = function nameOf(heading) {
|
|
161
|
+
return screen.getByRole('heading', {
|
|
162
|
+
name: heading
|
|
163
|
+
}).id;
|
|
164
|
+
};
|
|
165
|
+
it('keeps its name when a second title unmounts', function () {
|
|
166
|
+
var _render = render(twoTitles()),
|
|
167
|
+
rerender = _render.rerender;
|
|
168
|
+
rerender(twoTitles({
|
|
169
|
+
second: false
|
|
170
|
+
}));
|
|
171
|
+
expect(panel()).toHaveAttribute('aria-labelledby', nameOf('Drawer title'));
|
|
172
|
+
});
|
|
173
|
+
it('does not promote an earlier title when its text changes', function () {
|
|
174
|
+
var _render2 = render(twoTitles()),
|
|
175
|
+
rerender = _render2.rerender;
|
|
176
|
+
expect(panel()).toHaveAttribute('aria-labelledby', nameOf('Second title'));
|
|
177
|
+
rerender(twoTitles({
|
|
178
|
+
first: 'Renamed'
|
|
179
|
+
}));
|
|
180
|
+
expect(panel()).toHaveAttribute('aria-labelledby', nameOf('Second title'));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('Drawer popovers', function () {
|
|
184
|
+
it('gives its children a popover slot', function () {
|
|
185
|
+
renderDrawer();
|
|
186
|
+
expect(document.querySelector('.popover-slot')).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('Drawer accessible name', function () {
|
|
190
|
+
it('names the panel by the title Drawer.Title registers', function () {
|
|
191
|
+
renderDrawer();
|
|
192
|
+
var heading = screen.getByRole('heading', {
|
|
193
|
+
name: 'Drawer title'
|
|
194
|
+
});
|
|
195
|
+
expect(panel()).toHaveAttribute('aria-labelledby', heading.id);
|
|
196
|
+
expect(screen.getByRole('dialog', {
|
|
197
|
+
name: 'Drawer title'
|
|
198
|
+
})).toBeInTheDocument();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// RTL's render() flushes passive effects inside act() and would hide the
|
|
202
|
+
// difference, so this drives React itself.
|
|
203
|
+
it('names the panel in the first painted frame', function () {
|
|
204
|
+
var wasActEnvironment = global.IS_REACT_ACT_ENVIRONMENT;
|
|
205
|
+
global.IS_REACT_ACT_ENVIRONMENT = false;
|
|
206
|
+
var container = document.body.appendChild(document.createElement('div'));
|
|
207
|
+
var root = createRoot(container);
|
|
208
|
+
try {
|
|
209
|
+
flushSync(function () {
|
|
210
|
+
return root.render(drawerTree());
|
|
211
|
+
});
|
|
212
|
+
expect(panel()).toHaveAttribute('aria-labelledby', document.querySelector('.newspack-drawer__title').id);
|
|
213
|
+
expect(document.querySelector('.newspack-drawer__dismiss')).toHaveAttribute('aria-label', 'Close Drawer title');
|
|
214
|
+
} finally {
|
|
215
|
+
flushSync(function () {
|
|
216
|
+
return root.unmount();
|
|
217
|
+
});
|
|
218
|
+
container.remove();
|
|
219
|
+
global.IS_REACT_ACT_ENVIRONMENT = wasActEnvironment;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
it('names the panel by contentLabel when there is no title', function () {
|
|
223
|
+
renderDrawer({
|
|
224
|
+
header: false,
|
|
225
|
+
rootProps: {
|
|
226
|
+
contentLabel: 'Settings panel'
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
expect(panel()).toHaveAttribute('aria-label', 'Settings panel');
|
|
230
|
+
});
|
|
231
|
+
it('warns when it has neither a title nor a contentLabel', function () {
|
|
232
|
+
var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
|
|
233
|
+
renderDrawer({
|
|
234
|
+
header: false
|
|
235
|
+
});
|
|
236
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('contentLabel'));
|
|
237
|
+
warn.mockRestore();
|
|
238
|
+
});
|
|
239
|
+
it('does not warn when a Drawer.Title is rendered', function () {
|
|
240
|
+
var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
|
|
241
|
+
renderDrawer();
|
|
242
|
+
expect(warn).not.toHaveBeenCalled();
|
|
243
|
+
warn.mockRestore();
|
|
244
|
+
});
|
|
245
|
+
it('names its own confirmation, whose heading never shows', function () {
|
|
246
|
+
renderDrawer({
|
|
247
|
+
rootProps: {
|
|
248
|
+
isDirty: true
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
fireEvent.click(closeButton());
|
|
252
|
+
var confirmation = screen.getByRole('dialog', {
|
|
253
|
+
name: 'Unsaved changes'
|
|
254
|
+
});
|
|
255
|
+
expect(confirmation).not.toBe(panel());
|
|
256
|
+
expect(confirmation).toHaveClass('newspack-modal--hide-title');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
var bodyOf = function bodyOf(node) {
|
|
260
|
+
return node.parentElement;
|
|
261
|
+
};
|
|
262
|
+
describe('Drawer sections', function () {
|
|
263
|
+
it('groups consecutive content sections into one scroll container', function () {
|
|
264
|
+
renderDrawer({
|
|
265
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
266
|
+
children: "One"
|
|
267
|
+
}, "one"), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
268
|
+
children: "Two"
|
|
269
|
+
}, "two")],
|
|
270
|
+
footer: /*#__PURE__*/_jsx(Drawer.Footer, {
|
|
271
|
+
children: "Actions"
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
var one = screen.getByText('One');
|
|
275
|
+
expect(one).toHaveClass('newspack-drawer__content');
|
|
276
|
+
expect(bodyOf(one)).toHaveClass('newspack-drawer__body');
|
|
277
|
+
expect(bodyOf(screen.getByText('Two'))).toBe(bodyOf(one));
|
|
278
|
+
expect(bodyOf(one)).not.toContainElement(document.querySelector('.newspack-drawer__header'));
|
|
279
|
+
expect(bodyOf(one)).not.toContainElement(screen.getByText('Actions'));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// `.newspack-drawer__body:has(+ .newspack-drawer__footer)` drops the last
|
|
283
|
+
// section's bottom padding, so only the body the footer abuts may reach it.
|
|
284
|
+
it('leaves the footer immediately after the last scroll container', function () {
|
|
285
|
+
renderDrawer({
|
|
286
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
287
|
+
children: "One"
|
|
288
|
+
}, "one"), /*#__PURE__*/_jsx("div", {
|
|
289
|
+
children: "Split"
|
|
290
|
+
}, "split"), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
291
|
+
children: "Two"
|
|
292
|
+
}, "two")],
|
|
293
|
+
footer: /*#__PURE__*/_jsx(Drawer.Footer, {
|
|
294
|
+
children: "Actions"
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
var bodies = document.querySelectorAll('.newspack-drawer__body');
|
|
298
|
+
var footer = document.querySelector('.newspack-drawer__footer');
|
|
299
|
+
expect(bodies).toHaveLength(2);
|
|
300
|
+
expect(footer.previousElementSibling).toBe(bodies[1]);
|
|
301
|
+
expect(bodies[0].nextElementSibling).not.toBe(footer);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// jsdom has no layout, so the overflow the body measures is stubbed on the node.
|
|
305
|
+
describe('keyboard access to the scroll container', function () {
|
|
306
|
+
var observers;
|
|
307
|
+
var setOverflow = function setOverflow(body, scrollHeight) {
|
|
308
|
+
Object.defineProperty(body, 'scrollHeight', {
|
|
309
|
+
configurable: true,
|
|
310
|
+
value: scrollHeight
|
|
311
|
+
});
|
|
312
|
+
Object.defineProperty(body, 'clientHeight', {
|
|
313
|
+
configurable: true,
|
|
314
|
+
value: 100
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
var mutationObservers;
|
|
318
|
+
beforeEach(function () {
|
|
319
|
+
observers = [];
|
|
320
|
+
mutationObservers = [];
|
|
321
|
+
global.ResizeObserver = /*#__PURE__*/function () {
|
|
322
|
+
function _class(callback) {
|
|
323
|
+
_classCallCheck(this, _class);
|
|
324
|
+
this.callback = callback;
|
|
325
|
+
this.boxes = [];
|
|
326
|
+
observers.push(this);
|
|
327
|
+
this.disconnect = jest.fn();
|
|
328
|
+
}
|
|
329
|
+
return _createClass(_class, [{
|
|
330
|
+
key: "observe",
|
|
331
|
+
value: function observe(target, options) {
|
|
332
|
+
this.boxes.push(options === null || options === void 0 ? void 0 : options.box);
|
|
333
|
+
}
|
|
334
|
+
}]);
|
|
335
|
+
}();
|
|
336
|
+
global.MutationObserver = /*#__PURE__*/function () {
|
|
337
|
+
function _class2(callback) {
|
|
338
|
+
_classCallCheck(this, _class2);
|
|
339
|
+
this.callback = callback;
|
|
340
|
+
mutationObservers.push(this);
|
|
341
|
+
this.disconnect = jest.fn();
|
|
342
|
+
}
|
|
343
|
+
return _createClass(_class2, [{
|
|
344
|
+
key: "observe",
|
|
345
|
+
value: function observe() {}
|
|
346
|
+
}]);
|
|
347
|
+
}();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// jsdom supplies MutationObserver, so restore it rather than deleting it.
|
|
351
|
+
var nativeMutationObserver = global.MutationObserver;
|
|
352
|
+
afterEach(function () {
|
|
353
|
+
delete global.ResizeObserver;
|
|
354
|
+
global.MutationObserver = nativeMutationObserver;
|
|
355
|
+
});
|
|
356
|
+
it('takes no tab stop while the content fits', function () {
|
|
357
|
+
renderDrawer({
|
|
358
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
359
|
+
children: "One"
|
|
360
|
+
}, "one")]
|
|
361
|
+
});
|
|
362
|
+
var body = bodyOf(screen.getByText('One'));
|
|
363
|
+
expect(body).not.toHaveAttribute('tabindex');
|
|
364
|
+
expect(body).not.toHaveAttribute('aria-label');
|
|
365
|
+
expect(body).not.toHaveAttribute('role');
|
|
366
|
+
});
|
|
367
|
+
it('becomes focusable and named once it overflows, and reverts when it fits again', function () {
|
|
368
|
+
renderDrawer({
|
|
369
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
370
|
+
children: "One"
|
|
371
|
+
}, "one")]
|
|
372
|
+
});
|
|
373
|
+
var body = bodyOf(screen.getByText('One'));
|
|
374
|
+
setOverflow(body, 400);
|
|
375
|
+
act(function () {
|
|
376
|
+
return observers.forEach(function (observer) {
|
|
377
|
+
return observer.callback();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
expect(body).toHaveAttribute('tabindex', '0');
|
|
381
|
+
expect(body).toHaveAttribute('aria-label', 'Scrollable section');
|
|
382
|
+
expect(body).toHaveAttribute('role', 'group');
|
|
383
|
+
setOverflow(body, 50);
|
|
384
|
+
act(function () {
|
|
385
|
+
return observers.forEach(function (observer) {
|
|
386
|
+
return observer.callback();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
expect(body).not.toHaveAttribute('tabindex');
|
|
390
|
+
expect(body).not.toHaveAttribute('aria-label');
|
|
391
|
+
expect(body).not.toHaveAttribute('role');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// A section's padding is an inline custom property, so it grows the border box
|
|
395
|
+
// without touching the content box a default observer would watch.
|
|
396
|
+
it('observes its sections by border box', function () {
|
|
397
|
+
renderDrawer({
|
|
398
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
399
|
+
children: "One"
|
|
400
|
+
}, "one")]
|
|
401
|
+
});
|
|
402
|
+
expect(observers.some(function (observer) {
|
|
403
|
+
return observer.boxes.includes('border-box');
|
|
404
|
+
})).toBe(true);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Setup disconnects once itself to re-observe, so count the extra teardown call.
|
|
408
|
+
it('disconnects its observer when the drawer goes away', function () {
|
|
409
|
+
var _renderDrawer = renderDrawer({
|
|
410
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
411
|
+
children: "One"
|
|
412
|
+
}, "one")]
|
|
413
|
+
}),
|
|
414
|
+
unmount = _renderDrawer.unmount;
|
|
415
|
+
expect(observers.length).toBeGreaterThan(0);
|
|
416
|
+
var before = observers.map(function (observer) {
|
|
417
|
+
return observer.disconnect.mock.calls.length;
|
|
418
|
+
});
|
|
419
|
+
unmount();
|
|
420
|
+
observers.forEach(function (observer, index) {
|
|
421
|
+
expect(observer.disconnect.mock.calls.length).toBeGreaterThan(before[index]);
|
|
422
|
+
});
|
|
423
|
+
// The MutationObserver holds a registration on the now-detached body.
|
|
424
|
+
expect(mutationObservers.length).toBeGreaterThan(0);
|
|
425
|
+
mutationObservers.forEach(function (observer) {
|
|
426
|
+
return expect(observer.disconnect).toHaveBeenCalled();
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// The `> * { flex: none }` rule that stops sections compressing depends on this.
|
|
432
|
+
it('lays the scroll container out as a flush VStack', function () {
|
|
433
|
+
renderDrawer({
|
|
434
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
435
|
+
children: "One"
|
|
436
|
+
}, "one")]
|
|
437
|
+
});
|
|
438
|
+
var body = bodyOf(screen.getByText('One'));
|
|
439
|
+
expect(body).toHaveClass('components-v-stack');
|
|
440
|
+
expect(window.getComputedStyle(body).gap).toBe('0');
|
|
441
|
+
expect(window.getComputedStyle(body).justifyContent).toBe('flex-start');
|
|
442
|
+
});
|
|
443
|
+
it('starts a new container after a non-content child', function () {
|
|
444
|
+
renderDrawer({
|
|
445
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
446
|
+
children: "One"
|
|
447
|
+
}, "one"), /*#__PURE__*/_jsx("div", {
|
|
448
|
+
children: "Notice"
|
|
449
|
+
}, "notice"), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
450
|
+
children: "Two"
|
|
451
|
+
}, "two")]
|
|
452
|
+
});
|
|
453
|
+
expect(bodyOf(screen.getByText('Two'))).not.toBe(bodyOf(screen.getByText('One')));
|
|
454
|
+
});
|
|
455
|
+
it('keeps a divider inside the container it divides', function () {
|
|
456
|
+
renderDrawer({
|
|
457
|
+
contents: [/*#__PURE__*/_jsx(Drawer.Content, {
|
|
458
|
+
children: "One"
|
|
459
|
+
}, "one"), /*#__PURE__*/_jsx(Drawer.Divider, {}, "rule"), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
460
|
+
children: "Two"
|
|
461
|
+
}, "two")]
|
|
462
|
+
});
|
|
463
|
+
var body = bodyOf(screen.getByText('One'));
|
|
464
|
+
expect(bodyOf(screen.getByText('Two'))).toBe(body);
|
|
465
|
+
expect(body).toContainElement(document.querySelector('.newspack-drawer__divider'));
|
|
466
|
+
});
|
|
467
|
+
it('keeps the same scroll container when a sibling above it toggles', function () {
|
|
468
|
+
var tree = function tree(withNotice) {
|
|
469
|
+
return /*#__PURE__*/_jsxs(Drawer.Root, {
|
|
470
|
+
isOpen: true,
|
|
471
|
+
contentLabel: "Settings panel",
|
|
472
|
+
onRequestClose: noop,
|
|
473
|
+
children: [withNotice && /*#__PURE__*/_jsx("div", {
|
|
474
|
+
children: "Notice"
|
|
475
|
+
}), /*#__PURE__*/_jsx(Drawer.Content, {
|
|
476
|
+
children: "Body"
|
|
477
|
+
})]
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
var _render3 = render(tree(false)),
|
|
481
|
+
rerender = _render3.rerender;
|
|
482
|
+
var body = bodyOf(screen.getByText('Body'));
|
|
483
|
+
rerender(tree(true));
|
|
484
|
+
expect(bodyOf(screen.getByText('Body'))).toBe(body);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// The dialog's X reuses the cancel label, so a role query for "Cancel" is
|
|
489
|
+
// ambiguous. Query the footer buttons by visible text.
|
|
490
|
+
var dialogButton = function dialogButton(name) {
|
|
491
|
+
return screen.getByText(name, {
|
|
492
|
+
selector: 'button'
|
|
493
|
+
});
|
|
494
|
+
};
|
|
495
|
+
describe('Drawer closing', function () {
|
|
496
|
+
it('closes on a close icon click when it is not dirty', function () {
|
|
497
|
+
var onRequestClose = jest.fn();
|
|
498
|
+
renderDrawer({
|
|
499
|
+
rootProps: {
|
|
500
|
+
onRequestClose: onRequestClose
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
fireEvent.click(closeButton());
|
|
504
|
+
expect(onRequestClose).toHaveBeenCalledTimes(1);
|
|
505
|
+
});
|
|
506
|
+
it('closes on Escape when it is not dirty', function () {
|
|
507
|
+
var onRequestClose = jest.fn();
|
|
508
|
+
renderDrawer({
|
|
509
|
+
rootProps: {
|
|
510
|
+
onRequestClose: onRequestClose
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
514
|
+
key: 'Escape'
|
|
515
|
+
});
|
|
516
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
517
|
+
});
|
|
518
|
+
it('closes on an Escape reported by code alone', function () {
|
|
519
|
+
var onRequestClose = jest.fn();
|
|
520
|
+
renderDrawer({
|
|
521
|
+
rootProps: {
|
|
522
|
+
onRequestClose: onRequestClose
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
526
|
+
code: 'Escape'
|
|
527
|
+
});
|
|
528
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
529
|
+
});
|
|
530
|
+
it('ignores an Escape that ends an IME composition', function () {
|
|
531
|
+
var onRequestClose = jest.fn();
|
|
532
|
+
renderDrawer({
|
|
533
|
+
rootProps: {
|
|
534
|
+
onRequestClose: onRequestClose
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
538
|
+
key: 'Escape',
|
|
539
|
+
isComposing: true
|
|
540
|
+
});
|
|
541
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
542
|
+
});
|
|
543
|
+
it('ignores an Escape a child has already handled', function () {
|
|
544
|
+
var onRequestClose = jest.fn();
|
|
545
|
+
renderDrawer({
|
|
546
|
+
rootProps: {
|
|
547
|
+
onRequestClose: onRequestClose
|
|
548
|
+
},
|
|
549
|
+
contents: /*#__PURE__*/_jsx(Drawer.Content, {
|
|
550
|
+
children: /*#__PURE__*/_jsx("button", {
|
|
551
|
+
onKeyDown: function onKeyDown(event) {
|
|
552
|
+
return event.preventDefault();
|
|
553
|
+
},
|
|
554
|
+
children: "child"
|
|
555
|
+
})
|
|
556
|
+
})
|
|
557
|
+
});
|
|
558
|
+
fireEvent.keyDown(screen.getByRole('button', {
|
|
559
|
+
name: 'child'
|
|
560
|
+
}), {
|
|
561
|
+
key: 'Escape'
|
|
562
|
+
});
|
|
563
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
564
|
+
});
|
|
565
|
+
it('confirms before closing when it is dirty', function () {
|
|
566
|
+
var onRequestClose = jest.fn();
|
|
567
|
+
renderDrawer({
|
|
568
|
+
rootProps: {
|
|
569
|
+
isDirty: true,
|
|
570
|
+
onRequestClose: onRequestClose
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
fireEvent.click(closeButton());
|
|
574
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
575
|
+
fireEvent.click(dialogButton('Discard Changes'));
|
|
576
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
577
|
+
});
|
|
578
|
+
it('confirms an Escape when it is dirty', function () {
|
|
579
|
+
var onRequestClose = jest.fn();
|
|
580
|
+
renderDrawer({
|
|
581
|
+
rootProps: {
|
|
582
|
+
isDirty: true,
|
|
583
|
+
onRequestClose: onRequestClose
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
587
|
+
key: 'Escape'
|
|
588
|
+
});
|
|
589
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
590
|
+
fireEvent.click(dialogButton('Discard Changes'));
|
|
591
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
592
|
+
});
|
|
593
|
+
it('stays open when a delegated confirmation opens', function () {
|
|
594
|
+
var onRequestClose = jest.fn();
|
|
595
|
+
var Delegating = function Delegating() {
|
|
596
|
+
var _useConfirmDialog = useConfirmDialog({
|
|
597
|
+
message: 'Discard?',
|
|
598
|
+
confirmButtonText: 'Discard Changes'
|
|
599
|
+
}),
|
|
600
|
+
confirmDialog = _useConfirmDialog.confirmDialog,
|
|
601
|
+
requestConfirm = _useConfirmDialog.requestConfirm;
|
|
602
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
603
|
+
children: [drawerTree({
|
|
604
|
+
rootProps: {
|
|
605
|
+
isDirty: true,
|
|
606
|
+
requestConfirm: requestConfirm,
|
|
607
|
+
onRequestClose: onRequestClose
|
|
608
|
+
}
|
|
609
|
+
}), confirmDialog]
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
render(/*#__PURE__*/_jsx(Delegating, {}));
|
|
613
|
+
fireEvent.click(closeButton());
|
|
614
|
+
expect(screen.getByText('Discard Changes', {
|
|
615
|
+
selector: 'button'
|
|
616
|
+
})).toBeInTheDocument();
|
|
617
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
618
|
+
expect(panel()).toBeInTheDocument();
|
|
619
|
+
fireEvent.click(screen.getByText('Discard Changes', {
|
|
620
|
+
selector: 'button'
|
|
621
|
+
}));
|
|
622
|
+
expect(onRequestClose).toHaveBeenCalledTimes(1);
|
|
623
|
+
});
|
|
624
|
+
it('reads the current isDirty when a modal above it dismisses', function () {
|
|
625
|
+
var onRequestClose = jest.fn();
|
|
626
|
+
var above = function above(_ref3) {
|
|
627
|
+
var sibling = _ref3.sibling,
|
|
628
|
+
isDirty = _ref3.isDirty;
|
|
629
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
630
|
+
children: [sibling && /*#__PURE__*/_jsx(Modal, {
|
|
631
|
+
title: "Above",
|
|
632
|
+
onRequestClose: noop,
|
|
633
|
+
children: "Above"
|
|
634
|
+
}), drawerTree({
|
|
635
|
+
rootProps: {
|
|
636
|
+
isDirty: isDirty,
|
|
637
|
+
onRequestClose: onRequestClose
|
|
638
|
+
}
|
|
639
|
+
})]
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
var _render4 = render(above({
|
|
643
|
+
sibling: false,
|
|
644
|
+
isDirty: false
|
|
645
|
+
})),
|
|
646
|
+
rerender = _render4.rerender;
|
|
647
|
+
rerender(above({
|
|
648
|
+
sibling: true,
|
|
649
|
+
isDirty: true
|
|
650
|
+
}));
|
|
651
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
652
|
+
});
|
|
653
|
+
it('warns when a delegated confirmation closes without prompting', function () {
|
|
654
|
+
var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
|
|
655
|
+
var onRequestClose = jest.fn();
|
|
656
|
+
var requestConfirm = function requestConfirm(callback) {
|
|
657
|
+
return callback();
|
|
658
|
+
};
|
|
659
|
+
renderDrawer({
|
|
660
|
+
rootProps: {
|
|
661
|
+
isDirty: true,
|
|
662
|
+
requestConfirm: requestConfirm,
|
|
663
|
+
onRequestClose: onRequestClose
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
fireEvent.click(closeButton());
|
|
667
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
668
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('without prompting'));
|
|
669
|
+
warn.mockRestore();
|
|
670
|
+
});
|
|
671
|
+
it('does not warn when a delegated confirmation prompts', function () {
|
|
672
|
+
var warn = jest.spyOn(console, 'warn').mockImplementation(function () {});
|
|
673
|
+
renderDrawer({
|
|
674
|
+
rootProps: {
|
|
675
|
+
isDirty: true,
|
|
676
|
+
requestConfirm: jest.fn(),
|
|
677
|
+
onRequestClose: noop
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
fireEvent.click(closeButton());
|
|
681
|
+
expect(warn).not.toHaveBeenCalled();
|
|
682
|
+
warn.mockRestore();
|
|
683
|
+
});
|
|
684
|
+
it('leaves a real delegated confirmation to its owner', function () {
|
|
685
|
+
var error = jest.spyOn(console, 'error').mockImplementation(function () {});
|
|
686
|
+
var Delegating = function Delegating(_ref4) {
|
|
687
|
+
var open = _ref4.open;
|
|
688
|
+
var _useConfirmDialog2 = useConfirmDialog({
|
|
689
|
+
message: 'Discard?',
|
|
690
|
+
confirmButtonText: 'Discard Changes'
|
|
691
|
+
}),
|
|
692
|
+
confirmDialog = _useConfirmDialog2.confirmDialog,
|
|
693
|
+
requestConfirm = _useConfirmDialog2.requestConfirm;
|
|
694
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
695
|
+
children: [drawerTree({
|
|
696
|
+
rootProps: {
|
|
697
|
+
isOpen: open,
|
|
698
|
+
isDirty: true,
|
|
699
|
+
requestConfirm: requestConfirm,
|
|
700
|
+
onRequestClose: noop
|
|
701
|
+
}
|
|
702
|
+
}), confirmDialog]
|
|
703
|
+
});
|
|
704
|
+
};
|
|
705
|
+
var _render5 = render(/*#__PURE__*/_jsx(Delegating, {
|
|
706
|
+
open: true
|
|
707
|
+
})),
|
|
708
|
+
rerender = _render5.rerender;
|
|
709
|
+
fireEvent.click(closeButton());
|
|
710
|
+
expect(screen.getByText('Discard Changes', {
|
|
711
|
+
selector: 'button'
|
|
712
|
+
})).toBeInTheDocument();
|
|
713
|
+
rerender(/*#__PURE__*/_jsx(Delegating, {
|
|
714
|
+
open: false
|
|
715
|
+
}));
|
|
716
|
+
expect(screen.getByText('Discard Changes', {
|
|
717
|
+
selector: 'button'
|
|
718
|
+
})).toBeInTheDocument();
|
|
719
|
+
expect(error).not.toHaveBeenCalled();
|
|
720
|
+
error.mockRestore();
|
|
721
|
+
});
|
|
722
|
+
var withSibling = function withSibling(rootProps, sibling) {
|
|
723
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
724
|
+
children: [drawerTree({
|
|
725
|
+
rootProps: rootProps
|
|
726
|
+
}), sibling && /*#__PURE__*/_jsx(Modal, {
|
|
727
|
+
title: "Elsewhere",
|
|
728
|
+
onRequestClose: noop,
|
|
729
|
+
children: "Elsewhere"
|
|
730
|
+
})]
|
|
731
|
+
});
|
|
732
|
+
};
|
|
733
|
+
it('closes without confirming when another modal replaces it', function () {
|
|
734
|
+
var onRequestClose = jest.fn();
|
|
735
|
+
var rootProps = {
|
|
736
|
+
onRequestClose: onRequestClose
|
|
737
|
+
};
|
|
738
|
+
var _render6 = render(withSibling(rootProps, false)),
|
|
739
|
+
rerender = _render6.rerender;
|
|
740
|
+
rerender(withSibling(rootProps, true));
|
|
741
|
+
expect(onRequestClose).toHaveBeenCalledTimes(1);
|
|
742
|
+
});
|
|
743
|
+
it('stays open behind another modal when it is dirty', function () {
|
|
744
|
+
var onRequestClose = jest.fn();
|
|
745
|
+
var rootProps = {
|
|
746
|
+
isDirty: true,
|
|
747
|
+
onRequestClose: onRequestClose
|
|
748
|
+
};
|
|
749
|
+
var _render7 = render(withSibling(rootProps, false)),
|
|
750
|
+
rerender = _render7.rerender;
|
|
751
|
+
rerender(withSibling(rootProps, true));
|
|
752
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
753
|
+
expect(panel()).toBeInTheDocument();
|
|
754
|
+
expect(screen.queryByText('Discard Changes', {
|
|
755
|
+
selector: 'button'
|
|
756
|
+
})).not.toBeInTheDocument();
|
|
757
|
+
});
|
|
758
|
+
it('stays open when the confirmation is cancelled', function () {
|
|
759
|
+
var onRequestClose = jest.fn();
|
|
760
|
+
renderDrawer({
|
|
761
|
+
rootProps: {
|
|
762
|
+
isDirty: true,
|
|
763
|
+
onRequestClose: onRequestClose
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
fireEvent.click(closeButton());
|
|
767
|
+
fireEvent.click(dialogButton('Cancel'));
|
|
768
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
769
|
+
expect(screen.getByText('Body')).toBeInTheDocument();
|
|
770
|
+
});
|
|
771
|
+
it('delegates to requestConfirm when one is supplied', function () {
|
|
772
|
+
var onRequestClose = jest.fn();
|
|
773
|
+
var requestConfirm = jest.fn();
|
|
774
|
+
renderDrawer({
|
|
775
|
+
rootProps: {
|
|
776
|
+
isDirty: true,
|
|
777
|
+
requestConfirm: requestConfirm,
|
|
778
|
+
onRequestClose: onRequestClose
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
fireEvent.click(closeButton());
|
|
782
|
+
expect(requestConfirm).toHaveBeenCalledTimes(1);
|
|
783
|
+
expect(screen.queryByText('Discard Changes', {
|
|
784
|
+
selector: 'button'
|
|
785
|
+
})).not.toBeInTheDocument();
|
|
786
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
787
|
+
requestConfirm.mock.calls[0][0]();
|
|
788
|
+
expect(onRequestClose).toHaveBeenCalledTimes(1);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// jsdom has no PointerEvent, and a plain Event drops the `button` the handler
|
|
792
|
+
// checks. A MouseEvent carries one.
|
|
793
|
+
var pointerEvent = function pointerEvent(type) {
|
|
794
|
+
var button = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
795
|
+
return new MouseEvent(type, {
|
|
796
|
+
bubbles: true,
|
|
797
|
+
cancelable: true,
|
|
798
|
+
button: button
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
var pointerDownEvent = function pointerDownEvent(button) {
|
|
802
|
+
return pointerEvent('pointerdown', button);
|
|
803
|
+
};
|
|
804
|
+
var pointerDown = function pointerDown(node, button) {
|
|
805
|
+
return fireEvent(node, pointerDownEvent(button));
|
|
806
|
+
};
|
|
807
|
+
var pointerUp = function pointerUp(node, button) {
|
|
808
|
+
return fireEvent(node, pointerEvent('pointerup', button));
|
|
809
|
+
};
|
|
810
|
+
var press = function press(node, button) {
|
|
811
|
+
pointerDown(node, button);
|
|
812
|
+
pointerUp(node, button);
|
|
813
|
+
};
|
|
814
|
+
it('closes on an overlay press', function () {
|
|
815
|
+
var onRequestClose = jest.fn();
|
|
816
|
+
renderDrawer({
|
|
817
|
+
rootProps: {
|
|
818
|
+
onRequestClose: onRequestClose
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
press(overlay());
|
|
822
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
823
|
+
});
|
|
824
|
+
it('does not close on a press inside the panel', function () {
|
|
825
|
+
var onRequestClose = jest.fn();
|
|
826
|
+
renderDrawer({
|
|
827
|
+
rootProps: {
|
|
828
|
+
onRequestClose: onRequestClose
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
press(screen.getByText('Body'));
|
|
832
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
833
|
+
});
|
|
834
|
+
it('closes on an overlay press that spans a re-render', function () {
|
|
835
|
+
var _onRequestClose = jest.fn();
|
|
836
|
+
var tree = function tree() {
|
|
837
|
+
return drawerTree({
|
|
838
|
+
rootProps: {
|
|
839
|
+
onRequestClose: function onRequestClose() {
|
|
840
|
+
return _onRequestClose();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
};
|
|
845
|
+
var _render8 = render(tree()),
|
|
846
|
+
rerender = _render8.rerender;
|
|
847
|
+
pointerDown(overlay());
|
|
848
|
+
rerender(tree());
|
|
849
|
+
pointerUp(overlay());
|
|
850
|
+
expect(_onRequestClose).toHaveBeenCalled();
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Not the commit-versus-paint timing the ref mirror guards: act() drains passive
|
|
854
|
+
// effects, so this passes either way.
|
|
855
|
+
it('confirms when the drawer turns dirty mid-gesture', function () {
|
|
856
|
+
var onRequestClose = jest.fn();
|
|
857
|
+
var tree = function tree(isDirty) {
|
|
858
|
+
return drawerTree({
|
|
859
|
+
rootProps: {
|
|
860
|
+
onRequestClose: onRequestClose,
|
|
861
|
+
isDirty: isDirty
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
};
|
|
865
|
+
var _render9 = render(tree(false)),
|
|
866
|
+
rerender = _render9.rerender;
|
|
867
|
+
pointerDown(overlay());
|
|
868
|
+
rerender(tree(true));
|
|
869
|
+
pointerUp(overlay());
|
|
870
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
871
|
+
expect(dialogButton('Discard Changes')).toBeInTheDocument();
|
|
872
|
+
});
|
|
873
|
+
it('does not close when the press ends inside the panel', function () {
|
|
874
|
+
var onRequestClose = jest.fn();
|
|
875
|
+
renderDrawer({
|
|
876
|
+
rootProps: {
|
|
877
|
+
onRequestClose: onRequestClose
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
pointerDown(overlay());
|
|
881
|
+
pointerUp(screen.getByText('Body'));
|
|
882
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
883
|
+
});
|
|
884
|
+
it('does not close on a release that lands outside the overlay', function () {
|
|
885
|
+
var onRequestClose = jest.fn();
|
|
886
|
+
renderDrawer({
|
|
887
|
+
rootProps: {
|
|
888
|
+
onRequestClose: onRequestClose
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
pointerDown(overlay());
|
|
892
|
+
pointerUp(document.body);
|
|
893
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
894
|
+
pointerUp(overlay());
|
|
895
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// jsdom implements neither capture method, hence the stubs.
|
|
899
|
+
it('releases the implicit pointer capture on an overlay press', function () {
|
|
900
|
+
renderDrawer();
|
|
901
|
+
var scrim = overlay();
|
|
902
|
+
scrim.hasPointerCapture = jest.fn(function () {
|
|
903
|
+
return true;
|
|
904
|
+
});
|
|
905
|
+
scrim.releasePointerCapture = jest.fn();
|
|
906
|
+
var event = pointerDownEvent();
|
|
907
|
+
event.pointerId = 7;
|
|
908
|
+
fireEvent(scrim, event);
|
|
909
|
+
expect(scrim.releasePointerCapture).toHaveBeenCalledWith(7);
|
|
910
|
+
});
|
|
911
|
+
it('does not close when the press is cancelled', function () {
|
|
912
|
+
var onRequestClose = jest.fn();
|
|
913
|
+
renderDrawer({
|
|
914
|
+
rootProps: {
|
|
915
|
+
onRequestClose: onRequestClose
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
pointerDown(overlay());
|
|
919
|
+
fireEvent(overlay(), pointerEvent('pointercancel'));
|
|
920
|
+
pointerUp(overlay());
|
|
921
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
922
|
+
});
|
|
923
|
+
it('does not close on a non-primary overlay press', function () {
|
|
924
|
+
var onRequestClose = jest.fn();
|
|
925
|
+
renderDrawer({
|
|
926
|
+
rootProps: {
|
|
927
|
+
onRequestClose: onRequestClose
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
press(overlay(), 2);
|
|
931
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
932
|
+
});
|
|
933
|
+
it('prevents the default on an overlay press', function () {
|
|
934
|
+
renderDrawer();
|
|
935
|
+
var event = pointerDownEvent();
|
|
936
|
+
fireEvent(overlay(), event);
|
|
937
|
+
expect(event.defaultPrevented).toBe(true);
|
|
938
|
+
});
|
|
939
|
+
describe('during the exit animation', function () {
|
|
940
|
+
var closing = function closing(rootProps) {
|
|
941
|
+
var _render0 = render(drawerTree({
|
|
942
|
+
rootProps: rootProps
|
|
943
|
+
})),
|
|
944
|
+
rerender = _render0.rerender;
|
|
945
|
+
rerender(drawerTree({
|
|
946
|
+
rootProps: _objectSpread(_objectSpread({}, rootProps), {}, {
|
|
947
|
+
isOpen: false
|
|
948
|
+
})
|
|
949
|
+
}));
|
|
950
|
+
expect(panel()).toBeInTheDocument();
|
|
951
|
+
};
|
|
952
|
+
it('ignores a further Escape', function () {
|
|
953
|
+
var onRequestClose = jest.fn();
|
|
954
|
+
closing({
|
|
955
|
+
onRequestClose: onRequestClose
|
|
956
|
+
});
|
|
957
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
958
|
+
key: 'Escape'
|
|
959
|
+
});
|
|
960
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
961
|
+
});
|
|
962
|
+
it('ignores a further overlay press', function () {
|
|
963
|
+
var onRequestClose = jest.fn();
|
|
964
|
+
closing({
|
|
965
|
+
onRequestClose: onRequestClose
|
|
966
|
+
});
|
|
967
|
+
press(overlay());
|
|
968
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
969
|
+
});
|
|
970
|
+
it('marks the frame inert', function () {
|
|
971
|
+
var _render1 = render(drawerTree()),
|
|
972
|
+
rerender = _render1.rerender;
|
|
973
|
+
expect(panel()).not.toHaveAttribute('inert');
|
|
974
|
+
rerender(drawerTree({
|
|
975
|
+
rootProps: {
|
|
976
|
+
isOpen: false
|
|
977
|
+
}
|
|
978
|
+
}));
|
|
979
|
+
expect(panel()).toHaveAttribute('inert');
|
|
980
|
+
});
|
|
981
|
+
it('does not reopen the confirmation of a dirty drawer', function () {
|
|
982
|
+
var onRequestClose = jest.fn();
|
|
983
|
+
closing({
|
|
984
|
+
isDirty: true,
|
|
985
|
+
onRequestClose: onRequestClose
|
|
986
|
+
});
|
|
987
|
+
fireEvent.keyDown(screen.getByText('Body'), {
|
|
988
|
+
key: 'Escape'
|
|
989
|
+
});
|
|
990
|
+
expect(screen.queryByText('Discard Changes', {
|
|
991
|
+
selector: 'button'
|
|
992
|
+
})).not.toBeInTheDocument();
|
|
993
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
// jsdom emulates none of inert's behaviour; these observe the explicit return.
|
|
999
|
+
describe('Drawer focus return', function () {
|
|
1000
|
+
// Plain DOM: RTL's cleanup would fight a React-owned node removed by hand below.
|
|
1001
|
+
var opener;
|
|
1002
|
+
var elsewhere;
|
|
1003
|
+
beforeEach(function () {
|
|
1004
|
+
opener = document.body.appendChild(document.createElement('button'));
|
|
1005
|
+
elsewhere = document.body.appendChild(document.createElement('button'));
|
|
1006
|
+
opener.focus();
|
|
1007
|
+
});
|
|
1008
|
+
afterEach(function () {
|
|
1009
|
+
opener.remove();
|
|
1010
|
+
elsewhere.remove();
|
|
1011
|
+
});
|
|
1012
|
+
var closed = {
|
|
1013
|
+
rootProps: {
|
|
1014
|
+
isOpen: false
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
it('returns focus to the opener as the exit begins', function () {
|
|
1018
|
+
var _render10 = render(drawerTree(closed)),
|
|
1019
|
+
rerender = _render10.rerender;
|
|
1020
|
+
rerender(drawerTree());
|
|
1021
|
+
closeButton().focus();
|
|
1022
|
+
expect(panel()).toContainElement(document.activeElement);
|
|
1023
|
+
rerender(drawerTree(closed));
|
|
1024
|
+
expect(panel()).toBeInTheDocument();
|
|
1025
|
+
expect(document.activeElement).toBe(opener);
|
|
1026
|
+
});
|
|
1027
|
+
it('leaves focus on the opener once the panel unmounts', function () {
|
|
1028
|
+
var _render11 = render(drawerTree(closed)),
|
|
1029
|
+
rerender = _render11.rerender;
|
|
1030
|
+
rerender(drawerTree());
|
|
1031
|
+
rerender(drawerTree(closed));
|
|
1032
|
+
fireEvent.animationEnd(document.querySelector('.components-modal__frame'));
|
|
1033
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1034
|
+
expect(document.activeElement).toBe(opener);
|
|
1035
|
+
});
|
|
1036
|
+
it('closes without complaint when the opener has gone', function () {
|
|
1037
|
+
var _render12 = render(drawerTree(closed)),
|
|
1038
|
+
rerender = _render12.rerender;
|
|
1039
|
+
rerender(drawerTree());
|
|
1040
|
+
opener.remove();
|
|
1041
|
+
expect(function () {
|
|
1042
|
+
return rerender(drawerTree(closed));
|
|
1043
|
+
}).not.toThrow();
|
|
1044
|
+
expect(panel()).toBeInTheDocument();
|
|
1045
|
+
});
|
|
1046
|
+
it('leaves focus alone when it has moved outside the panel', function () {
|
|
1047
|
+
var _render13 = render(drawerTree(closed)),
|
|
1048
|
+
rerender = _render13.rerender;
|
|
1049
|
+
rerender(drawerTree());
|
|
1050
|
+
elsewhere.focus();
|
|
1051
|
+
rerender(drawerTree(closed));
|
|
1052
|
+
expect(document.activeElement).toBe(elsewhere);
|
|
1053
|
+
});
|
|
1054
|
+
var editor = function editor(editing, child) {
|
|
1055
|
+
return /*#__PURE__*/_jsx(Drawer.Root, {
|
|
1056
|
+
isOpen: !!editing,
|
|
1057
|
+
contentLabel: "Editor",
|
|
1058
|
+
onRequestClose: noop,
|
|
1059
|
+
children: /*#__PURE__*/_jsx(Drawer.Content, {
|
|
1060
|
+
children: editing && child
|
|
1061
|
+
})
|
|
1062
|
+
});
|
|
1063
|
+
};
|
|
1064
|
+
it('returns focus to the opener when the focused child unmounts with the close', function () {
|
|
1065
|
+
var _render14 = render(editor(false, /*#__PURE__*/_jsx("input", {
|
|
1066
|
+
"aria-label": "Name"
|
|
1067
|
+
}))),
|
|
1068
|
+
rerender = _render14.rerender;
|
|
1069
|
+
rerender(editor(true, /*#__PURE__*/_jsx("input", {
|
|
1070
|
+
"aria-label": "Name"
|
|
1071
|
+
})));
|
|
1072
|
+
screen.getByLabelText('Name').focus();
|
|
1073
|
+
expect(panel()).toContainElement(document.activeElement);
|
|
1074
|
+
rerender(editor(false, /*#__PURE__*/_jsx("input", {
|
|
1075
|
+
"aria-label": "Name"
|
|
1076
|
+
})));
|
|
1077
|
+
expect(screen.queryByLabelText('Name')).not.toBeInTheDocument();
|
|
1078
|
+
expect(document.activeElement).toBe(opener);
|
|
1079
|
+
});
|
|
1080
|
+
it('restores focus to the panel when it reopens mid-exit', function () {
|
|
1081
|
+
var _render15 = render(drawerTree(closed)),
|
|
1082
|
+
rerender = _render15.rerender;
|
|
1083
|
+
rerender(drawerTree());
|
|
1084
|
+
rerender(drawerTree(closed));
|
|
1085
|
+
expect(document.activeElement).toBe(opener);
|
|
1086
|
+
rerender(drawerTree());
|
|
1087
|
+
expect(panel()).not.toHaveAttribute('inert');
|
|
1088
|
+
expect(panel()).toContainElement(document.activeElement);
|
|
1089
|
+
expect(document.activeElement).not.toBe(opener);
|
|
1090
|
+
});
|
|
1091
|
+
it('leaves focus on content that claims it when it reopens mid-exit', function () {
|
|
1092
|
+
var SelfFocusing = function SelfFocusing() {
|
|
1093
|
+
var ref = useRef(null);
|
|
1094
|
+
useLayoutEffect(function () {
|
|
1095
|
+
var _ref$current;
|
|
1096
|
+
return (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.focus();
|
|
1097
|
+
}, []);
|
|
1098
|
+
return /*#__PURE__*/_jsx("input", {
|
|
1099
|
+
"aria-label": "Name",
|
|
1100
|
+
ref: ref
|
|
1101
|
+
});
|
|
1102
|
+
};
|
|
1103
|
+
var _render16 = render(editor(false, /*#__PURE__*/_jsx(SelfFocusing, {}))),
|
|
1104
|
+
rerender = _render16.rerender;
|
|
1105
|
+
rerender(editor(true, /*#__PURE__*/_jsx(SelfFocusing, {})));
|
|
1106
|
+
rerender(editor(false, /*#__PURE__*/_jsx(SelfFocusing, {})));
|
|
1107
|
+
expect(document.activeElement).toBe(opener);
|
|
1108
|
+
rerender(editor(true, /*#__PURE__*/_jsx(SelfFocusing, {})));
|
|
1109
|
+
expect(document.activeElement).toBe(screen.getByLabelText('Name'));
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
describe('Drawer closed from the parent while its confirmation is open', function () {
|
|
1113
|
+
var opener;
|
|
1114
|
+
beforeEach(function () {
|
|
1115
|
+
opener = document.body.appendChild(document.createElement('button'));
|
|
1116
|
+
opener.focus();
|
|
1117
|
+
});
|
|
1118
|
+
afterEach(function () {
|
|
1119
|
+
opener.remove();
|
|
1120
|
+
});
|
|
1121
|
+
var dirty = {
|
|
1122
|
+
isDirty: true,
|
|
1123
|
+
onRequestClose: noop
|
|
1124
|
+
};
|
|
1125
|
+
var confirmingThenClosed = function confirmingThenClosed() {
|
|
1126
|
+
var _render17 = render(drawerTree({
|
|
1127
|
+
rootProps: dirty
|
|
1128
|
+
})),
|
|
1129
|
+
rerender = _render17.rerender;
|
|
1130
|
+
fireEvent.click(closeButton());
|
|
1131
|
+
expect(dialogButton('Discard Changes')).toBeInTheDocument();
|
|
1132
|
+
rerender(drawerTree({
|
|
1133
|
+
rootProps: _objectSpread(_objectSpread({}, dirty), {}, {
|
|
1134
|
+
isOpen: false
|
|
1135
|
+
})
|
|
1136
|
+
}));
|
|
1137
|
+
return rerender;
|
|
1138
|
+
};
|
|
1139
|
+
var confirmation = function confirmation() {
|
|
1140
|
+
return screen.queryByText('Discard Changes', {
|
|
1141
|
+
selector: 'button'
|
|
1142
|
+
});
|
|
1143
|
+
};
|
|
1144
|
+
it('dismisses the confirmation', function () {
|
|
1145
|
+
confirmingThenClosed();
|
|
1146
|
+
expect(confirmation()).not.toBeInTheDocument();
|
|
1147
|
+
});
|
|
1148
|
+
it('returns focus to the opener', function () {
|
|
1149
|
+
confirmingThenClosed();
|
|
1150
|
+
expect(document.activeElement).toBe(opener);
|
|
1151
|
+
});
|
|
1152
|
+
it('leaves focus on the opener once the panel unmounts', function () {
|
|
1153
|
+
confirmingThenClosed();
|
|
1154
|
+
fireEvent.animationEnd(document.querySelector('.components-modal__frame'));
|
|
1155
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1156
|
+
expect(document.activeElement).toBe(opener);
|
|
1157
|
+
});
|
|
1158
|
+
it('does not restore the confirmation when it opens again', function () {
|
|
1159
|
+
var rerender = confirmingThenClosed();
|
|
1160
|
+
fireEvent.animationEnd(document.querySelector('.components-modal__frame'));
|
|
1161
|
+
rerender(drawerTree({
|
|
1162
|
+
rootProps: dirty
|
|
1163
|
+
}));
|
|
1164
|
+
expect(confirmation()).not.toBeInTheDocument();
|
|
1165
|
+
});
|
|
1166
|
+
it('returns focus to the opener under prefers-reduced-motion', function () {
|
|
1167
|
+
useReducedMotion.mockReturnValue(true);
|
|
1168
|
+
confirmingThenClosed();
|
|
1169
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1170
|
+
expect(confirmation()).not.toBeInTheDocument();
|
|
1171
|
+
expect(document.activeElement).toBe(opener);
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
describe('Drawer confirmation inside a router', function () {
|
|
1175
|
+
var renderRouted = function renderRouted(rootProps) {
|
|
1176
|
+
return render(/*#__PURE__*/_jsx(MemoryRouter, {
|
|
1177
|
+
children: drawerTree({
|
|
1178
|
+
rootProps: rootProps
|
|
1179
|
+
})
|
|
1180
|
+
}));
|
|
1181
|
+
};
|
|
1182
|
+
it('confirms a close', function () {
|
|
1183
|
+
var onRequestClose = jest.fn();
|
|
1184
|
+
renderRouted({
|
|
1185
|
+
isDirty: true,
|
|
1186
|
+
onRequestClose: onRequestClose
|
|
1187
|
+
});
|
|
1188
|
+
fireEvent.click(closeButton());
|
|
1189
|
+
fireEvent.click(dialogButton('Discard Changes'));
|
|
1190
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
1191
|
+
});
|
|
1192
|
+
it('cancels a close, and can still confirm afterwards', function () {
|
|
1193
|
+
var onRequestClose = jest.fn();
|
|
1194
|
+
renderRouted({
|
|
1195
|
+
isDirty: true,
|
|
1196
|
+
onRequestClose: onRequestClose
|
|
1197
|
+
});
|
|
1198
|
+
fireEvent.click(closeButton());
|
|
1199
|
+
fireEvent.click(dialogButton('Cancel'));
|
|
1200
|
+
expect(onRequestClose).not.toHaveBeenCalled();
|
|
1201
|
+
fireEvent.click(closeButton());
|
|
1202
|
+
fireEvent.click(dialogButton('Discard Changes'));
|
|
1203
|
+
expect(onRequestClose).toHaveBeenCalled();
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// jsdom has no AnimationEvent, so `fireEvent.animationEnd` cannot carry a name.
|
|
1208
|
+
var animationEnd = function animationEnd(node, animationName) {
|
|
1209
|
+
var event = new Event('animationend', {
|
|
1210
|
+
bubbles: true
|
|
1211
|
+
});
|
|
1212
|
+
event.animationName = animationName;
|
|
1213
|
+
fireEvent(node, event);
|
|
1214
|
+
};
|
|
1215
|
+
describe('Drawer exit animation', function () {
|
|
1216
|
+
afterEach(function () {
|
|
1217
|
+
jest.useRealTimers();
|
|
1218
|
+
});
|
|
1219
|
+
var closed = {
|
|
1220
|
+
rootProps: {
|
|
1221
|
+
isOpen: false
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
it('marks the frame as exiting, then removes it', function () {
|
|
1225
|
+
jest.useFakeTimers();
|
|
1226
|
+
var _render18 = render(drawerTree()),
|
|
1227
|
+
rerender = _render18.rerender;
|
|
1228
|
+
rerender(drawerTree(closed));
|
|
1229
|
+
expect(panel()).toHaveClass('components-modal__frame', 'is-exiting');
|
|
1230
|
+
act(function () {
|
|
1231
|
+
jest.runAllTimers();
|
|
1232
|
+
});
|
|
1233
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1234
|
+
});
|
|
1235
|
+
it('keeps the same frame across a close', function () {
|
|
1236
|
+
var _render19 = render(drawerTree()),
|
|
1237
|
+
rerender = _render19.rerender;
|
|
1238
|
+
var opened = panel();
|
|
1239
|
+
rerender(drawerTree(closed));
|
|
1240
|
+
expect(panel()).toBe(opened);
|
|
1241
|
+
});
|
|
1242
|
+
it('marks the overlay as exiting', function () {
|
|
1243
|
+
jest.useFakeTimers();
|
|
1244
|
+
var _render20 = render(drawerTree()),
|
|
1245
|
+
rerender = _render20.rerender;
|
|
1246
|
+
rerender(drawerTree(closed));
|
|
1247
|
+
expect(overlay()).toHaveClass('is-exiting');
|
|
1248
|
+
act(function () {
|
|
1249
|
+
jest.runAllTimers();
|
|
1250
|
+
});
|
|
1251
|
+
expect(overlay()).not.toBeInTheDocument();
|
|
1252
|
+
});
|
|
1253
|
+
it('ignores an animationend from the overlay fade', function () {
|
|
1254
|
+
var _render21 = render(drawerTree()),
|
|
1255
|
+
rerender = _render21.rerender;
|
|
1256
|
+
rerender(drawerTree(closed));
|
|
1257
|
+
animationEnd(overlay(), 'newspack-drawer-overlay-fade-out');
|
|
1258
|
+
expect(panel()).toBeInTheDocument();
|
|
1259
|
+
animationEnd(document.querySelector('.components-modal__frame'), 'newspack-drawer-slide-out');
|
|
1260
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1261
|
+
});
|
|
1262
|
+
it('unmounts the panel when its frame finishes animating out', function () {
|
|
1263
|
+
var _render22 = render(drawerTree()),
|
|
1264
|
+
rerender = _render22.rerender;
|
|
1265
|
+
rerender(drawerTree(closed));
|
|
1266
|
+
fireEvent.animationEnd(document.querySelector('.components-modal__frame'));
|
|
1267
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1268
|
+
});
|
|
1269
|
+
it('ignores an animationend from a nested animation', function () {
|
|
1270
|
+
var _render23 = render(drawerTree()),
|
|
1271
|
+
rerender = _render23.rerender;
|
|
1272
|
+
rerender(drawerTree(closed));
|
|
1273
|
+
animationEnd(screen.getByText('Body'), 'components-popover__appear');
|
|
1274
|
+
expect(panel()).toBeInTheDocument();
|
|
1275
|
+
animationEnd(panel(), 'newspack-drawer-slide-out');
|
|
1276
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1277
|
+
});
|
|
1278
|
+
it('removes the panel at once under prefers-reduced-motion', function () {
|
|
1279
|
+
useReducedMotion.mockReturnValue(true);
|
|
1280
|
+
var _render24 = render(drawerTree()),
|
|
1281
|
+
rerender = _render24.rerender;
|
|
1282
|
+
rerender(drawerTree(closed));
|
|
1283
|
+
expect(panel()).not.toBeInTheDocument();
|
|
1284
|
+
});
|
|
1285
|
+
});
|