newspack-components 4.8.0 → 4.8.1-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 +9 -0
- package/DEVELOPMENT.md +18 -17
- package/dist/cjs/action-card/index.js +85 -18
- package/dist/cjs/action-card/index.test.js +192 -5
- package/dist/cjs/action-card/style.scss +3 -9
- package/dist/cjs/debug-badge/index.js +44 -0
- package/dist/cjs/debug-badge/index.test.js +50 -0
- package/dist/cjs/debug-badge/style.scss +26 -0
- package/dist/cjs/empty-state/index.test.js +37 -24
- package/dist/cjs/empty-state/root.js +2 -1
- package/dist/cjs/footer/index.js +52 -16
- package/dist/cjs/footer/index.test.js +208 -0
- package/dist/cjs/footer/style.scss +17 -1
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/notice/index.js +1 -4
- package/dist/cjs/notice/style.scss +0 -19
- package/dist/cjs/plugin-toggle/index.js +1 -1
- package/dist/cjs/with-wizard-screen/index.js +1 -3
- package/dist/cjs/with-wizard-screen/index.test.js +0 -3
- package/dist/cjs/wizard/index.js +8 -7
- package/dist/cjs/wizard/index.test.js +158 -5
- package/dist/cjs/wizard/store/index.js +2 -0
- package/dist/esm/action-card/index.js +86 -21
- package/dist/esm/action-card/index.test.js +192 -1
- package/dist/esm/action-card/style.scss +3 -9
- package/dist/esm/debug-badge/index.js +39 -0
- package/dist/esm/debug-badge/index.test.js +46 -0
- package/dist/esm/debug-badge/style.scss +26 -0
- package/dist/esm/empty-state/index.test.js +37 -24
- package/dist/esm/empty-state/root.js +2 -1
- package/dist/esm/footer/index.js +52 -17
- package/dist/esm/footer/index.test.js +210 -0
- package/dist/esm/footer/style.scss +17 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/notice/index.js +2 -5
- package/dist/esm/notice/style.scss +0 -19
- package/dist/esm/plugin-toggle/index.js +1 -1
- package/dist/esm/with-wizard-screen/index.js +2 -4
- package/dist/esm/with-wizard-screen/index.test.js +0 -3
- package/dist/esm/wizard/index.js +11 -10
- package/dist/esm/wizard/index.test.js +158 -5
- package/dist/esm/wizard/store/index.js +2 -0
- package/package.json +1 -1
- package/src/action-card/action-card.d.ts +3 -2
- package/src/action-card/index.js +81 -8
- package/src/action-card/index.test.js +159 -0
- package/src/action-card/style.scss +3 -9
- package/src/debug-badge/index.js +36 -0
- package/src/debug-badge/index.test.js +39 -0
- package/src/debug-badge/style.scss +26 -0
- package/src/empty-state/README.md +11 -10
- package/src/empty-state/index.test.js +10 -0
- package/src/empty-state/root.tsx +2 -1
- package/src/empty-state/types.ts +1 -1
- package/src/footer/index.js +47 -2
- package/src/footer/index.test.js +114 -0
- package/src/footer/style.scss +17 -1
- package/src/index.js +1 -0
- package/src/notice/index.js +2 -17
- package/src/notice/style.scss +0 -19
- package/src/plugin-toggle/index.js +1 -1
- package/src/with-wizard-screen/index.js +2 -2
- package/src/with-wizard-screen/index.test.js +0 -2
- package/src/wizard/index.js +11 -9
- package/src/wizard/index.test.js +96 -3
- package/src/wizard/store/index.js +2 -0
|
@@ -13,9 +13,6 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
13
13
|
* Internal dependencies.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
global.newspack_aux_data = {
|
|
17
|
-
is_debug_mode: false
|
|
18
|
-
};
|
|
19
16
|
describe('withWizardScreen', function () {
|
|
20
17
|
it('falls back to a single headerText breadcrumb when no breadcrumbItems are provided', function () {
|
|
21
18
|
var WrappedComponent = function WrappedComponent() {
|
package/dist/cjs/wizard/index.js
CHANGED
|
@@ -26,8 +26,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
26
26
|
* External dependencies.
|
|
27
27
|
*/ /**
|
|
28
28
|
* WordPress dependencies.
|
|
29
|
-
*/
|
|
30
|
-
/**
|
|
29
|
+
*/ /**
|
|
31
30
|
* Internal dependencies
|
|
32
31
|
*/
|
|
33
32
|
(0, _store["default"])();
|
|
@@ -96,7 +95,10 @@ var ResetHeaderData = function ResetHeaderData() {
|
|
|
96
95
|
var location = useLocation();
|
|
97
96
|
var _useDispatch = (0, _data.useDispatch)(_store.WIZARD_STORE_NAMESPACE),
|
|
98
97
|
resetHeaderData = _useDispatch.resetHeaderData;
|
|
99
|
-
|
|
98
|
+
|
|
99
|
+
// Must stay before paint: a passive effect here would run after a section that
|
|
100
|
+
// publishes from a layout effect, wiping the header it just set.
|
|
101
|
+
(0, _element.useLayoutEffect)(function () {
|
|
100
102
|
resetHeaderData();
|
|
101
103
|
window.scrollTo(0, 0);
|
|
102
104
|
}, [location.pathname, resetHeaderData]);
|
|
@@ -202,6 +204,7 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
202
204
|
var actions = headerData.actions,
|
|
203
205
|
backNav = headerData.backNav,
|
|
204
206
|
badges = headerData.badges,
|
|
207
|
+
headerFullWidth = headerData.fullWidth,
|
|
205
208
|
sectionDescription = headerData.sectionDescription,
|
|
206
209
|
sectionMenu = headerData.sectionMenu,
|
|
207
210
|
sectionName = headerData.sectionName,
|
|
@@ -338,7 +341,7 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
338
341
|
render: function render(routerProps) {
|
|
339
342
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
|
|
340
343
|
className: (0, _classnames["default"])('newspack-wizard__content', className, {
|
|
341
|
-
'newspack-wizard__content--full-width': section.fullWidth
|
|
344
|
+
'newspack-wizard__content--full-width': headerFullWidth !== null && headerFullWidth !== void 0 ? headerFullWidth : section.fullWidth
|
|
342
345
|
}),
|
|
343
346
|
children: ['function' === typeof renderAboveSections ? renderAboveSections() : null, (sectionTitle || section.title) && /*#__PURE__*/(0, _jsxRuntime.jsx)(_.SectionHeader, {
|
|
344
347
|
className: "newspack-wizard__section-header",
|
|
@@ -419,9 +422,7 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
419
422
|
}),
|
|
420
423
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(HashRouter, {
|
|
421
424
|
hashType: "slash",
|
|
422
|
-
children: [
|
|
423
|
-
debugMode: true
|
|
424
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(WizardHeaderRegion, {
|
|
425
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_.DebugBadge, {}), /*#__PURE__*/(0, _jsxRuntime.jsx)(WizardHeaderRegion, {
|
|
425
426
|
hideHeader: hideHeader,
|
|
426
427
|
headerText: headerText,
|
|
427
428
|
sections: routedSections,
|
|
@@ -5,8 +5,11 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
|
|
|
5
5
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
6
6
|
var _react = require("@testing-library/react");
|
|
7
7
|
var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch"));
|
|
8
|
+
var _data = require("@wordpress/data");
|
|
9
|
+
var _element = require("@wordpress/element");
|
|
8
10
|
var _ = _interopRequireDefault(require("./"));
|
|
9
11
|
var _utils = require("./store/utils");
|
|
12
|
+
var _store = require("./store");
|
|
10
13
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
14
|
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
12
15
|
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); } /**
|
|
@@ -20,14 +23,12 @@ jest.mock('@wordpress/api-fetch', function () {
|
|
|
20
23
|
return jest.fn();
|
|
21
24
|
});
|
|
22
25
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
window.newspack_aux_data = {
|
|
26
|
-
is_debug_mode: false
|
|
27
|
-
};
|
|
26
|
+
// Localized onto every real wizard screen; the footer's ExternalLink needs a
|
|
27
|
+
// string href.
|
|
28
28
|
window.newspack_urls = {
|
|
29
29
|
support: 'https://help.newspack.com/'
|
|
30
30
|
};
|
|
31
|
+
window.scrollTo = jest.fn();
|
|
31
32
|
var SETTINGS = {
|
|
32
33
|
minimumDonation: '5'
|
|
33
34
|
};
|
|
@@ -249,4 +250,156 @@ describe('Wizard', function () {
|
|
|
249
250
|
}
|
|
250
251
|
}, _callee4);
|
|
251
252
|
})));
|
|
253
|
+
});
|
|
254
|
+
var NarrowingSection = function NarrowingSection() {
|
|
255
|
+
var _useDispatch = (0, _data.useDispatch)(_store.WIZARD_STORE_NAMESPACE),
|
|
256
|
+
setHeaderData = _useDispatch.setHeaderData;
|
|
257
|
+
(0, _element.useLayoutEffect)(function () {
|
|
258
|
+
setHeaderData({
|
|
259
|
+
fullWidth: false
|
|
260
|
+
});
|
|
261
|
+
}, [setHeaderData]);
|
|
262
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
263
|
+
children: "Narrowing section"
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
var RecoveringSection = function RecoveringSection() {
|
|
267
|
+
var _useDispatch2 = (0, _data.useDispatch)(_store.WIZARD_STORE_NAMESPACE),
|
|
268
|
+
setHeaderData = _useDispatch2.setHeaderData;
|
|
269
|
+
var _useState = (0, _element.useState)(true),
|
|
270
|
+
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
271
|
+
failed = _useState2[0],
|
|
272
|
+
setFailed = _useState2[1];
|
|
273
|
+
(0, _element.useLayoutEffect)(function () {
|
|
274
|
+
setHeaderData({
|
|
275
|
+
fullWidth: failed ? false : undefined
|
|
276
|
+
});
|
|
277
|
+
}, [setHeaderData, failed]);
|
|
278
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("button", {
|
|
279
|
+
onClick: function onClick() {
|
|
280
|
+
return setFailed(false);
|
|
281
|
+
},
|
|
282
|
+
children: "Retry"
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Two sections, because that is what mounts ResetHeaderData: a single-section
|
|
287
|
+
// wizard never exercises the reset these overrides have to survive.
|
|
288
|
+
var widthSections = function widthSections(renderSection) {
|
|
289
|
+
return [{
|
|
290
|
+
label: 'List',
|
|
291
|
+
path: '/',
|
|
292
|
+
exact: true,
|
|
293
|
+
fullWidth: true,
|
|
294
|
+
render: renderSection
|
|
295
|
+
}, {
|
|
296
|
+
label: 'Other',
|
|
297
|
+
path: '/other',
|
|
298
|
+
render: function render() {
|
|
299
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
300
|
+
children: "Other section"
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}];
|
|
304
|
+
};
|
|
305
|
+
describe('Wizard content width', function () {
|
|
306
|
+
beforeEach(function () {
|
|
307
|
+
_apiFetch["default"].mockReset();
|
|
308
|
+
});
|
|
309
|
+
it('renders full-width when the section declares it and nothing overrides it', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee5() {
|
|
310
|
+
var _render, container, _t5;
|
|
311
|
+
return _regenerator().w(function (_context5) {
|
|
312
|
+
while (1) switch (_context5.n) {
|
|
313
|
+
case 0:
|
|
314
|
+
_render = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
315
|
+
headerText: "Test wizard",
|
|
316
|
+
sections: widthSections(function () {
|
|
317
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
318
|
+
children: "List content"
|
|
319
|
+
});
|
|
320
|
+
})
|
|
321
|
+
})), container = _render.container;
|
|
322
|
+
_t5 = expect;
|
|
323
|
+
_context5.n = 1;
|
|
324
|
+
return _react.screen.findByText('List content');
|
|
325
|
+
case 1:
|
|
326
|
+
_t5(_context5.v).toBeInTheDocument();
|
|
327
|
+
expect(container.querySelector('.newspack-wizard__content')).toHaveClass('newspack-wizard__content--full-width');
|
|
328
|
+
case 2:
|
|
329
|
+
return _context5.a(2);
|
|
330
|
+
}
|
|
331
|
+
}, _callee5);
|
|
332
|
+
})));
|
|
333
|
+
it('falls back to the standard width when a section overrides fullWidth before paint', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee6() {
|
|
334
|
+
var _render2, container, _t6;
|
|
335
|
+
return _regenerator().w(function (_context6) {
|
|
336
|
+
while (1) switch (_context6.n) {
|
|
337
|
+
case 0:
|
|
338
|
+
_render2 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
339
|
+
headerText: "Test wizard",
|
|
340
|
+
sections: widthSections(function () {
|
|
341
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NarrowingSection, {});
|
|
342
|
+
})
|
|
343
|
+
})), container = _render2.container;
|
|
344
|
+
_t6 = expect;
|
|
345
|
+
_context6.n = 1;
|
|
346
|
+
return _react.screen.findByText('Narrowing section');
|
|
347
|
+
case 1:
|
|
348
|
+
_t6(_context6.v).toBeInTheDocument();
|
|
349
|
+
expect(container.querySelector('.newspack-wizard__content')).not.toHaveClass('newspack-wizard__content--full-width');
|
|
350
|
+
case 2:
|
|
351
|
+
return _context6.a(2);
|
|
352
|
+
}
|
|
353
|
+
}, _callee6);
|
|
354
|
+
})));
|
|
355
|
+
it('restores the declared width when the section clears its override', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee7() {
|
|
356
|
+
var _render3, container, retry;
|
|
357
|
+
return _regenerator().w(function (_context7) {
|
|
358
|
+
while (1) switch (_context7.n) {
|
|
359
|
+
case 0:
|
|
360
|
+
_render3 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
361
|
+
headerText: "Test wizard",
|
|
362
|
+
sections: widthSections(function () {
|
|
363
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(RecoveringSection, {});
|
|
364
|
+
})
|
|
365
|
+
})), container = _render3.container;
|
|
366
|
+
_context7.n = 1;
|
|
367
|
+
return _react.screen.findByRole('button', {
|
|
368
|
+
name: 'Retry'
|
|
369
|
+
});
|
|
370
|
+
case 1:
|
|
371
|
+
retry = _context7.v;
|
|
372
|
+
expect(container.querySelector('.newspack-wizard__content')).not.toHaveClass('newspack-wizard__content--full-width');
|
|
373
|
+
_react.fireEvent.click(retry);
|
|
374
|
+
_context7.n = 2;
|
|
375
|
+
return (0, _react.waitFor)(function () {
|
|
376
|
+
return expect(container.querySelector('.newspack-wizard__content')).toHaveClass('newspack-wizard__content--full-width');
|
|
377
|
+
});
|
|
378
|
+
case 2:
|
|
379
|
+
return _context7.a(2);
|
|
380
|
+
}
|
|
381
|
+
}, _callee7);
|
|
382
|
+
})));
|
|
383
|
+
it('keeps a section header published from a layout effect through the mount reset', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee8() {
|
|
384
|
+
var _t7;
|
|
385
|
+
return _regenerator().w(function (_context8) {
|
|
386
|
+
while (1) switch (_context8.n) {
|
|
387
|
+
case 0:
|
|
388
|
+
(0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
389
|
+
headerText: "Test wizard",
|
|
390
|
+
sections: widthSections(function () {
|
|
391
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NarrowingSection, {});
|
|
392
|
+
})
|
|
393
|
+
}));
|
|
394
|
+
_t7 = expect;
|
|
395
|
+
_context8.n = 1;
|
|
396
|
+
return _react.screen.findByText('Narrowing section');
|
|
397
|
+
case 1:
|
|
398
|
+
_t7(_context8.v).toBeInTheDocument();
|
|
399
|
+
expect((0, _data.select)(_store.WIZARD_STORE_NAMESPACE).getHeaderData().fullWidth).toBe(false);
|
|
400
|
+
case 2:
|
|
401
|
+
return _context8.a(2);
|
|
402
|
+
}
|
|
403
|
+
}, _callee8);
|
|
404
|
+
})));
|
|
252
405
|
});
|
|
@@ -23,6 +23,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
23
23
|
*/
|
|
24
24
|
var WIZARD_STORE_NAMESPACE = exports.WIZARD_STORE_NAMESPACE = 'newspack/wizards';
|
|
25
25
|
var DEFAULT_STATE = {
|
|
26
|
+
// `fullWidth` is deliberately absent: a default here would narrow every
|
|
27
|
+
// full-width section on reset, since Wizard only falls back when it is unset.
|
|
26
28
|
headerData: {
|
|
27
29
|
actions: [],
|
|
28
30
|
backNav: '',
|
|
@@ -9,8 +9,9 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
9
9
|
/**
|
|
10
10
|
* WordPress dependencies
|
|
11
11
|
*/
|
|
12
|
-
import { Draggable, ExternalLink, ToggleControl } from '@wordpress/components';
|
|
13
|
-
import { useEffect, useState } from '@wordpress/element';
|
|
12
|
+
import { Draggable, ExternalLink, Notice, ToggleControl } from '@wordpress/components';
|
|
13
|
+
import { RawHTML, useEffect, useState } from '@wordpress/element';
|
|
14
|
+
import { decodeEntities } from '@wordpress/html-entities';
|
|
14
15
|
import { __ } from '@wordpress/i18n';
|
|
15
16
|
import { Icon, check, chevronDown, chevronUp, dragHandle } from '@wordpress/icons';
|
|
16
17
|
import { Badge } from '@wordpress/ui';
|
|
@@ -18,20 +19,90 @@ import { Badge } from '@wordpress/ui';
|
|
|
18
19
|
/**
|
|
19
20
|
* Internal dependencies
|
|
20
21
|
*/
|
|
21
|
-
import { Button, Card, Grid, Handoff,
|
|
22
|
+
import { Button, Card, Grid, Handoff, Waiting } from '../';
|
|
22
23
|
import './style.scss';
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* External dependencies
|
|
26
27
|
*/
|
|
27
28
|
import classnames from 'classnames';
|
|
29
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
30
|
+
var NOTIFICATION_LEVELS = ['error', 'info', 'success', 'warning'];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Collect a notification's text, including text nested inside React elements.
|
|
34
|
+
*
|
|
35
|
+
* Segments join with a space so that adjacent elements do not weld together: by the
|
|
36
|
+
* time speak() sees this string the markup that separated them is already gone.
|
|
37
|
+
*
|
|
38
|
+
* @param {*} value Notification content.
|
|
39
|
+
* @return {string} The collected text.
|
|
40
|
+
*/
|
|
41
|
+
var _getNotificationText = function getNotificationText(value) {
|
|
42
|
+
var _value$props;
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === 'number') {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
if (value instanceof Error) {
|
|
50
|
+
return value.message;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.map(_getNotificationText).join(' ');
|
|
54
|
+
}
|
|
55
|
+
return (value === null || value === void 0 || (_value$props = value.props) === null || _value$props === void 0 ? void 0 : _value$props.children) === undefined ? '' : _getNotificationText(value.props.children);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Derive a plain-text announcement from a notification.
|
|
60
|
+
*
|
|
61
|
+
* Notice defaults spokenMessage to its children and runs renderToString over them
|
|
62
|
+
* during render, which corrupts the hook dispatcher when those children are
|
|
63
|
+
* components. A notification can be any element, so the announcement is always
|
|
64
|
+
* built from its text instead of letting that default apply. Tags in an HTML
|
|
65
|
+
* notification stay in, since speak() strips them itself. Entities are decoded so
|
|
66
|
+
* the announcement matches the text on screen, which getNotificationContent decodes
|
|
67
|
+
* for the same reason.
|
|
68
|
+
*
|
|
69
|
+
* @param {*} notification Notification content.
|
|
70
|
+
* @return {string} Message to announce.
|
|
71
|
+
*/
|
|
72
|
+
var getSpokenNotification = function getSpokenNotification(notification) {
|
|
73
|
+
return decodeEntities(_getNotificationText(notification)).replace(/\s+/g, ' ').trim();
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Decode a notification's own text for display.
|
|
78
|
+
*
|
|
79
|
+
* React does not decode entities in text children, and notification text is often
|
|
80
|
+
* server-sourced, so a message carrying `’` would otherwise show the entity
|
|
81
|
+
* and announce the character. The HTML branch is left alone: there the browser's
|
|
82
|
+
* parser decodes as it renders.
|
|
83
|
+
*
|
|
84
|
+
* @param {*} value Notification content.
|
|
85
|
+
* @return {*} The content, with its own strings decoded.
|
|
86
|
+
*/
|
|
87
|
+
var _getNotificationContent = function getNotificationContent(value) {
|
|
88
|
+
if (value instanceof Error) {
|
|
89
|
+
return decodeEntities(value.message);
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === 'string') {
|
|
92
|
+
return decodeEntities(value);
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.map(_getNotificationContent);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
};
|
|
28
99
|
|
|
29
100
|
/**
|
|
30
101
|
* ActionCard component
|
|
102
|
+
*
|
|
31
103
|
* @param {ActionCardProps} props Component props.
|
|
32
104
|
* @return {JSX.Element} ActionCard component.
|
|
33
105
|
*/
|
|
34
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
35
106
|
var ActionCard = function ActionCard(_ref) {
|
|
36
107
|
var badges = _ref.badges,
|
|
37
108
|
className = _ref.className,
|
|
@@ -125,6 +196,7 @@ var ActionCard = function ActionCard(_ref) {
|
|
|
125
196
|
}
|
|
126
197
|
}, [collapse]);
|
|
127
198
|
var hasChildren = notification || children;
|
|
199
|
+
var notificationContent = _getNotificationContent(notification);
|
|
128
200
|
var classes = classnames('newspack-action-card', simple && 'newspack-card--is-clickable', hasGreyHeader && 'newspack-card--has-grey-header', hasWhiteHeader && 'newspack-card--has-white-header', hasChildren && 'newspack-card--has-children', indent && 'newspack-card--indent', isSmall && 'is-small', isMedium && 'is-medium', checkbox && 'has-checkbox', expandable && 'is-expandable', draggable && 'is-draggable', actionContent && 'has-action-content', className);
|
|
129
201
|
var backgroundImageStyles = function backgroundImageStyles(url) {
|
|
130
202
|
return url ? {
|
|
@@ -248,24 +320,17 @@ var ActionCard = function ActionCard(_ref) {
|
|
|
248
320
|
width: 24
|
|
249
321
|
})
|
|
250
322
|
})]
|
|
251
|
-
}), notification && /*#__PURE__*/
|
|
323
|
+
}), notification && NOTIFICATION_LEVELS.includes(notificationLevel) && /*#__PURE__*/_jsx("div", {
|
|
252
324
|
className: "newspack-action-card__notification newspack-action-card__region-children",
|
|
253
|
-
children:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
isSuccess: true,
|
|
263
|
-
rawHTML: notificationHTML
|
|
264
|
-
}), 'warning' === notificationLevel && /*#__PURE__*/_jsx(Notice, {
|
|
265
|
-
noticeText: notification,
|
|
266
|
-
isWarning: true,
|
|
267
|
-
rawHTML: notificationHTML
|
|
268
|
-
})]
|
|
325
|
+
children: /*#__PURE__*/_jsx(Notice, {
|
|
326
|
+
status: notificationLevel,
|
|
327
|
+
isDismissible: false,
|
|
328
|
+
spokenMessage: getSpokenNotification(notification),
|
|
329
|
+
children: notificationHTML && typeof notification === 'string' ? /*#__PURE__*/_jsx(RawHTML, {
|
|
330
|
+
className: "newspack-action-card__notification-html",
|
|
331
|
+
children: notification
|
|
332
|
+
}) : notificationContent
|
|
333
|
+
})
|
|
269
334
|
}), children && (expandable && expanded || !expandable) ? /*#__PURE__*/_jsx("div", {
|
|
270
335
|
className: "newspack-action-card__region-children",
|
|
271
336
|
children: children
|
|
@@ -1,12 +1,28 @@
|
|
|
1
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
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; }
|
|
4
|
+
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; }
|
|
1
5
|
/**
|
|
2
6
|
* External dependencies.
|
|
3
7
|
*/
|
|
4
8
|
import { render, screen } from '@testing-library/react';
|
|
5
9
|
|
|
10
|
+
/**
|
|
11
|
+
* WordPress dependencies.
|
|
12
|
+
*/
|
|
13
|
+
import { speak } from '@wordpress/a11y';
|
|
14
|
+
import { useState } from '@wordpress/element';
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Internal dependencies.
|
|
8
18
|
*/
|
|
9
19
|
import ActionCard from './index';
|
|
20
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
21
|
+
jest.mock('@wordpress/a11y', function () {
|
|
22
|
+
return _objectSpread(_objectSpread({}, jest.requireActual('@wordpress/a11y')), {}, {
|
|
23
|
+
speak: jest.fn()
|
|
24
|
+
});
|
|
25
|
+
});
|
|
10
26
|
|
|
11
27
|
/**
|
|
12
28
|
* The library Badge styles its wrapper rather than its text, so a badge with no label
|
|
@@ -15,7 +31,6 @@ import ActionCard from './index';
|
|
|
15
31
|
* can sit on a placement with no entry in the placement map — and an array literal is
|
|
16
32
|
* always non-empty, so the component has to drop those itself.
|
|
17
33
|
*/
|
|
18
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
19
34
|
describe('ActionCard badges', function () {
|
|
20
35
|
var badgeText = function badgeText() {
|
|
21
36
|
return screen.queryAllByText(function (_, node) {
|
|
@@ -86,4 +101,180 @@ describe('ActionCard badges', function () {
|
|
|
86
101
|
// An empty array used to fall through as a literal `0` in the heading.
|
|
87
102
|
expect(withEmptyArray.textContent).not.toContain('0');
|
|
88
103
|
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* ActionCard derives the notice's spoken message rather than letting core default it to
|
|
108
|
+
* the children. Core runs `renderToString` over that default during render, which corrupts
|
|
109
|
+
* the hook dispatcher when the children are components — so the derivation is what keeps a
|
|
110
|
+
* notification carrying a `<Button>` from crashing the card on a later re-render.
|
|
111
|
+
*/
|
|
112
|
+
describe('ActionCard notifications', function () {
|
|
113
|
+
beforeEach(function () {
|
|
114
|
+
speak.mockClear();
|
|
115
|
+
});
|
|
116
|
+
var spoken = function spoken() {
|
|
117
|
+
return speak.mock.calls.map(function (call) {
|
|
118
|
+
return call[0];
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
it('renders a notice for a recognised level', function () {
|
|
122
|
+
var _render5 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
123
|
+
title: "Ad Manager",
|
|
124
|
+
notification: "Plugin cannot be installed",
|
|
125
|
+
notificationLevel: "error"
|
|
126
|
+
})),
|
|
127
|
+
container = _render5.container;
|
|
128
|
+
expect(container.querySelector('.components-notice.is-error')).toBeInTheDocument();
|
|
129
|
+
});
|
|
130
|
+
it('renders no notice for an unrecognised or absent level', function () {
|
|
131
|
+
var _render6 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
132
|
+
title: "Ad Manager",
|
|
133
|
+
notification: "Something happened",
|
|
134
|
+
notificationLevel: "critical"
|
|
135
|
+
})),
|
|
136
|
+
unknown = _render6.container;
|
|
137
|
+
expect(unknown.querySelector('.components-notice')).not.toBeInTheDocument();
|
|
138
|
+
var _render7 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
139
|
+
title: "Ad Manager",
|
|
140
|
+
notification: "Something happened"
|
|
141
|
+
})),
|
|
142
|
+
absent = _render7.container;
|
|
143
|
+
expect(absent.querySelector('.components-notice')).not.toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
it('announces the text inside an element-only notification', function () {
|
|
146
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
147
|
+
title: "Ad Manager",
|
|
148
|
+
notificationLevel: "error",
|
|
149
|
+
notification: [/*#__PURE__*/_jsx("button", {
|
|
150
|
+
children: "Click here to connect your account."
|
|
151
|
+
}, "connect")]
|
|
152
|
+
}));
|
|
153
|
+
expect(spoken()).toContain('Click here to connect your account.');
|
|
154
|
+
});
|
|
155
|
+
it('announces strings and element text together', function () {
|
|
156
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
157
|
+
title: "Ad Manager",
|
|
158
|
+
notificationLevel: "success",
|
|
159
|
+
notification: ['Created custom targeting keys: ',
|
|
160
|
+
/*#__PURE__*/
|
|
161
|
+
// eslint-disable-next-line react/jsx-indent
|
|
162
|
+
_jsx("a", {
|
|
163
|
+
href: "https://example.org",
|
|
164
|
+
children: "Visit your GAM dashboard"
|
|
165
|
+
}, "dashboard")]
|
|
166
|
+
}));
|
|
167
|
+
expect(spoken()).toContain('Created custom targeting keys: Visit your GAM dashboard');
|
|
168
|
+
});
|
|
169
|
+
it('announces an Error and renders its message rather than throwing', function () {
|
|
170
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
171
|
+
title: "Ad Manager",
|
|
172
|
+
notificationLevel: "error",
|
|
173
|
+
notification: new Error('Network unreachable')
|
|
174
|
+
}));
|
|
175
|
+
expect(screen.getByText('Network unreachable')).toBeInTheDocument();
|
|
176
|
+
expect(spoken()).toContain('Network unreachable');
|
|
177
|
+
});
|
|
178
|
+
it('renders HTML notifications in their own wrapper and hands speak() the tags to strip', function () {
|
|
179
|
+
var _render8 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
180
|
+
title: "Ad Manager",
|
|
181
|
+
notificationLevel: "error",
|
|
182
|
+
notification: '<p>Install failed.</p><p>Try again.</p>',
|
|
183
|
+
notificationHTML: true
|
|
184
|
+
})),
|
|
185
|
+
container = _render8.container;
|
|
186
|
+
expect(container.querySelectorAll('.newspack-action-card__notification-html > p')).toHaveLength(2);
|
|
187
|
+
expect(spoken()[0]).toContain('<p>Install failed.</p>');
|
|
188
|
+
});
|
|
189
|
+
it('decodes entities in both the announcement and the visible text', function () {
|
|
190
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
191
|
+
title: "Ad Manager",
|
|
192
|
+
notificationLevel: "error",
|
|
193
|
+
notification: 'Plugin “Foo” could not be activated'
|
|
194
|
+
}));
|
|
195
|
+
expect(screen.getByText('Plugin “Foo” could not be activated')).toBeInTheDocument();
|
|
196
|
+
expect(spoken()[0]).toContain('“Foo”');
|
|
197
|
+
});
|
|
198
|
+
it('decodes entities inside an array of notification parts', function () {
|
|
199
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
200
|
+
title: "Ad Manager",
|
|
201
|
+
notificationLevel: "error",
|
|
202
|
+
notification: ['Could not reach the publisher’s network.', ' ']
|
|
203
|
+
}));
|
|
204
|
+
expect(screen.getByText(/Could not reach the publisher’s network\./)).toBeInTheDocument();
|
|
205
|
+
expect(spoken()[0]).toContain('publisher’s');
|
|
206
|
+
});
|
|
207
|
+
it('leaves entity decoding to the browser on the HTML branch', function () {
|
|
208
|
+
var _render9 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
209
|
+
title: "Ad Manager",
|
|
210
|
+
notificationLevel: "error",
|
|
211
|
+
notification: 'Get it from <a href="https://example.org">the plugin’s site</a>',
|
|
212
|
+
notificationHTML: true
|
|
213
|
+
})),
|
|
214
|
+
container = _render9.container;
|
|
215
|
+
expect(container.querySelector('.newspack-action-card__notification-html a').textContent).toBe('the plugin’s site');
|
|
216
|
+
expect(spoken()[0]).toContain('plugin’s site');
|
|
217
|
+
});
|
|
218
|
+
it('renders a non-string notification as children even when notificationHTML is set', function () {
|
|
219
|
+
var _render0 = render(/*#__PURE__*/_jsx(ActionCard, {
|
|
220
|
+
title: "Ad Manager",
|
|
221
|
+
notificationLevel: "error",
|
|
222
|
+
notification: /*#__PURE__*/_jsx("span", {
|
|
223
|
+
children: "Network unreachable"
|
|
224
|
+
}),
|
|
225
|
+
notificationHTML: true
|
|
226
|
+
})),
|
|
227
|
+
container = _render0.container;
|
|
228
|
+
expect(screen.getByText('Network unreachable')).toBeInTheDocument();
|
|
229
|
+
expect(container.querySelector('.newspack-action-card__notification-html')).not.toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
it('keeps adjacent element texts apart in the announcement', function () {
|
|
232
|
+
render(/*#__PURE__*/_jsx(ActionCard, {
|
|
233
|
+
title: "Ad Manager",
|
|
234
|
+
notificationLevel: "error",
|
|
235
|
+
notification: [
|
|
236
|
+
/*#__PURE__*/
|
|
237
|
+
// eslint-disable-next-line react/jsx-indent
|
|
238
|
+
_jsx("a", {
|
|
239
|
+
href: "https://example.org",
|
|
240
|
+
children: "Visit your GAM dashboard"
|
|
241
|
+
}, "dashboard"), /*#__PURE__*/_jsx("button", {
|
|
242
|
+
type: "button",
|
|
243
|
+
children: "Click here to connect your account."
|
|
244
|
+
}, "connect")]
|
|
245
|
+
}));
|
|
246
|
+
expect(spoken()[0]).toBe('Visit your GAM dashboard Click here to connect your account.');
|
|
247
|
+
});
|
|
248
|
+
it('survives a re-render that changes how many hooked components the notification holds', function () {
|
|
249
|
+
var HookedAction = function HookedAction(_ref) {
|
|
250
|
+
var children = _ref.children;
|
|
251
|
+
var _useState = useState(children),
|
|
252
|
+
_useState2 = _slicedToArray(_useState, 1),
|
|
253
|
+
label = _useState2[0];
|
|
254
|
+
return /*#__PURE__*/_jsx("button", {
|
|
255
|
+
type: "button",
|
|
256
|
+
children: label
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
var card = function card(notification) {
|
|
260
|
+
return /*#__PURE__*/_jsx(ActionCard, {
|
|
261
|
+
title: "Ad Manager",
|
|
262
|
+
notificationLevel: "error",
|
|
263
|
+
notification: notification
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
var _render1 = render(card([/*#__PURE__*/_jsx(HookedAction, {
|
|
267
|
+
children: "Retry"
|
|
268
|
+
}, "retry"), /*#__PURE__*/_jsx(HookedAction, {
|
|
269
|
+
children: "Documentation"
|
|
270
|
+
}, "docs")])),
|
|
271
|
+
rerender = _render1.rerender,
|
|
272
|
+
container = _render1.container;
|
|
273
|
+
expect(function () {
|
|
274
|
+
return rerender(card([/*#__PURE__*/_jsx(HookedAction, {
|
|
275
|
+
children: "Retry"
|
|
276
|
+
}, "retry")]));
|
|
277
|
+
}).not.toThrow();
|
|
278
|
+
expect(container.querySelector('.components-notice.is-error')).toBeInTheDocument();
|
|
279
|
+
});
|
|
89
280
|
});
|
|
@@ -339,18 +339,12 @@
|
|
|
339
339
|
|
|
340
340
|
/* Notifications */
|
|
341
341
|
.newspack-action-card__notification {
|
|
342
|
-
.
|
|
342
|
+
.components-notice {
|
|
343
343
|
margin: 0;
|
|
344
|
-
|
|
345
|
-
&__heading {
|
|
346
|
-
margin-top: 0;
|
|
347
|
-
}
|
|
348
344
|
}
|
|
349
345
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
margin-top: 32px;
|
|
353
|
-
}
|
|
346
|
+
.newspack-action-card__notification-html > * {
|
|
347
|
+
margin: 0;
|
|
354
348
|
}
|
|
355
349
|
}
|
|
356
350
|
|