newspack-components 4.6.2-hotfix-wizard-stale-data-after-plugin-install.1 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/dist/cjs/grid/style.scss +21 -4
- package/dist/cjs/wizard/index.js +4 -27
- package/dist/esm/grid/style.scss +21 -4
- package/dist/esm/wizard/index.js +5 -28
- package/package.json +1 -1
- package/src/grid/style.scss +21 -4
- package/src/wizard/index.js +4 -27
- package/dist/cjs/wizard/index.test.js +0 -252
- package/dist/esm/wizard/index.test.js +0 -254
- package/src/wizard/index.test.js +0 -156
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
## newspack-components [4.6.2
|
|
1
|
+
## newspack-components [4.6.2](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.6.1...newspack-components@4.6.2) (2026-08-17)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
*
|
|
6
|
+
* three WordPress 7.1 regressions in Newsletters ([#876](https://github.com/Automattic/newspack-workspace/issues/876)) ([#894](https://github.com/Automattic/newspack-workspace/issues/894)) ([3b6ac6c](https://github.com/Automattic/newspack-workspace/commit/3b6ac6cce050f2d70f73df1d98f175de1f19a759))
|
|
7
7
|
|
|
8
8
|
## newspack-components [4.6.1](https://github.com/Automattic/newspack-workspace/compare/newspack-components@4.6.0...newspack-components@4.6.1) (2026-08-17)
|
|
9
9
|
|
package/dist/cjs/grid/style.scss
CHANGED
|
@@ -83,11 +83,13 @@
|
|
|
83
83
|
&__columns-4 {
|
|
84
84
|
grid-template-columns: repeat(2, 1fr);
|
|
85
85
|
|
|
86
|
-
> [start]
|
|
86
|
+
> [start],
|
|
87
|
+
> [data-start] {
|
|
87
88
|
grid-column-start: 1;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
> [end]
|
|
91
|
+
> [end],
|
|
92
|
+
> [data-end] {
|
|
91
93
|
grid-column-end: -1;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
@@ -108,11 +110,26 @@
|
|
|
108
110
|
&__columns-4 {
|
|
109
111
|
grid-template-columns: repeat(4, 1fr);
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
// Positioning is keyed off `data-` attributes because the emotion-styled
|
|
114
|
+
// components in newer `@wordpress/components` filter props through
|
|
115
|
+
// `@emotion/is-prop-valid` before they reach the DOM. Its allowlist
|
|
116
|
+
// carries `start` (a real attribute, on `<ol>`) but not `end`, so a
|
|
117
|
+
// `VStack` asking for `end` silently lost its `grid-column-end` and
|
|
118
|
+
// collapsed to one column (NEWS-2866). `data-` attributes are never
|
|
119
|
+
// filtered, so they hold whatever component renders them.
|
|
120
|
+
//
|
|
121
|
+
// This is a component-level filter, not a WordPress-wide one: a plain
|
|
122
|
+
// `<div start="2" end="4">` still emits both attributes. The bare
|
|
123
|
+
// selectors below are kept for those plain children — they do NOT
|
|
124
|
+
// rescue an emotion-styled consumer still passing `end`, which never
|
|
125
|
+
// reaches the DOM to be matched.
|
|
126
|
+
> [start="2"],
|
|
127
|
+
> [data-start="2"] {
|
|
112
128
|
grid-column-start: 2;
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
> [end="4"]
|
|
131
|
+
> [end="4"],
|
|
132
|
+
> [data-end="4"] {
|
|
116
133
|
grid-column-end: 4;
|
|
117
134
|
}
|
|
118
135
|
}
|
package/dist/cjs/wizard/index.js
CHANGED
|
@@ -160,8 +160,6 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
160
160
|
var notices = (0, _data.useSelect)(function (select) {
|
|
161
161
|
return select(_store.WIZARD_STORE_NAMESPACE).getNotices();
|
|
162
162
|
});
|
|
163
|
-
var _useDispatch2 = (0, _data.useDispatch)(_store.WIZARD_STORE_NAMESPACE),
|
|
164
|
-
invalidateResolution = _useDispatch2.invalidateResolution;
|
|
165
163
|
var actions = headerData.actions,
|
|
166
164
|
backNav = headerData.backNav,
|
|
167
165
|
badges = headerData.badges,
|
|
@@ -190,30 +188,6 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
190
188
|
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
191
189
|
pluginRequirementsSatisfied = _useState2[0],
|
|
192
190
|
setPluginRequirementsSatisfied = _useState2[1];
|
|
193
|
-
|
|
194
|
-
// The data fetch above runs once per mount, while the required plugins are
|
|
195
|
-
// still missing — endpoints that need them answer empty or error outright,
|
|
196
|
-
// and either way the resolver records that as the answer. So the installer
|
|
197
|
-
// has to trigger a refetch, or the section mounts against it and renders
|
|
198
|
-
// nothing until the user saves. Requirements already met on mount are left
|
|
199
|
-
// alone: that fetch saw the real site, and refetching would cost every such
|
|
200
|
-
// wizard a second request on every load.
|
|
201
|
-
var requirementsWereUnmet = (0, _element.useRef)(false);
|
|
202
|
-
var onPluginStatus = function onPluginStatus(_ref3) {
|
|
203
|
-
var complete = _ref3.complete;
|
|
204
|
-
// Leave the installer first: whatever happens to the refetch, the user
|
|
205
|
-
// should not be stranded on a required-plugins screen for a plugin they
|
|
206
|
-
// just installed.
|
|
207
|
-
setPluginRequirementsSatisfied(complete);
|
|
208
|
-
if (!complete) {
|
|
209
|
-
requirementsWereUnmet.current = true;
|
|
210
|
-
} else if (requirementsWereUnmet.current) {
|
|
211
|
-
requirementsWereUnmet.current = false;
|
|
212
|
-
if (apiSlug && isInitialFetchTriggered) {
|
|
213
|
-
invalidateResolution('getWizardAPIData', [apiSlug]);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
191
|
if (!pluginRequirementsSatisfied) {
|
|
218
192
|
headerText = requiredPlugins.length > 1 ? (0, _i18n.__)('Required plugins', 'newspack-plugin') : (0, _i18n.__)('Required plugin', 'newspack-plugin');
|
|
219
193
|
displayedSections = [{
|
|
@@ -221,7 +195,10 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
221
195
|
render: function render() {
|
|
222
196
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_.PluginInstaller, {
|
|
223
197
|
plugins: requiredPlugins,
|
|
224
|
-
onStatus:
|
|
198
|
+
onStatus: function onStatus(_ref3) {
|
|
199
|
+
var complete = _ref3.complete;
|
|
200
|
+
return setPluginRequirementsSatisfied(complete);
|
|
201
|
+
}
|
|
225
202
|
});
|
|
226
203
|
}
|
|
227
204
|
}];
|
package/dist/esm/grid/style.scss
CHANGED
|
@@ -83,11 +83,13 @@
|
|
|
83
83
|
&__columns-4 {
|
|
84
84
|
grid-template-columns: repeat(2, 1fr);
|
|
85
85
|
|
|
86
|
-
> [start]
|
|
86
|
+
> [start],
|
|
87
|
+
> [data-start] {
|
|
87
88
|
grid-column-start: 1;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
> [end]
|
|
91
|
+
> [end],
|
|
92
|
+
> [data-end] {
|
|
91
93
|
grid-column-end: -1;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
@@ -108,11 +110,26 @@
|
|
|
108
110
|
&__columns-4 {
|
|
109
111
|
grid-template-columns: repeat(4, 1fr);
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
// Positioning is keyed off `data-` attributes because the emotion-styled
|
|
114
|
+
// components in newer `@wordpress/components` filter props through
|
|
115
|
+
// `@emotion/is-prop-valid` before they reach the DOM. Its allowlist
|
|
116
|
+
// carries `start` (a real attribute, on `<ol>`) but not `end`, so a
|
|
117
|
+
// `VStack` asking for `end` silently lost its `grid-column-end` and
|
|
118
|
+
// collapsed to one column (NEWS-2866). `data-` attributes are never
|
|
119
|
+
// filtered, so they hold whatever component renders them.
|
|
120
|
+
//
|
|
121
|
+
// This is a component-level filter, not a WordPress-wide one: a plain
|
|
122
|
+
// `<div start="2" end="4">` still emits both attributes. The bare
|
|
123
|
+
// selectors below are kept for those plain children — they do NOT
|
|
124
|
+
// rescue an emotion-styled consumer still passing `end`, which never
|
|
125
|
+
// reaches the DOM to be matched.
|
|
126
|
+
> [start="2"],
|
|
127
|
+
> [data-start="2"] {
|
|
112
128
|
grid-column-start: 2;
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
> [end="4"]
|
|
131
|
+
> [end="4"],
|
|
132
|
+
> [data-end="4"] {
|
|
116
133
|
grid-column-end: 4;
|
|
117
134
|
}
|
|
118
135
|
}
|
package/dist/esm/wizard/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import classnames from 'classnames';
|
|
|
12
12
|
*/
|
|
13
13
|
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
|
|
14
14
|
import { useDispatch, useSelect } from '@wordpress/data';
|
|
15
|
-
import { cloneElement, isValidElement, useEffect,
|
|
15
|
+
import { cloneElement, isValidElement, useEffect, useState, forwardRef } from '@wordpress/element';
|
|
16
16
|
import { __ } from '@wordpress/i18n';
|
|
17
17
|
import { category, chevronLeft, moreVertical } from '@wordpress/icons';
|
|
18
18
|
|
|
@@ -157,8 +157,6 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
157
157
|
var notices = useSelect(function (select) {
|
|
158
158
|
return select(WIZARD_STORE_NAMESPACE).getNotices();
|
|
159
159
|
});
|
|
160
|
-
var _useDispatch2 = useDispatch(WIZARD_STORE_NAMESPACE),
|
|
161
|
-
invalidateResolution = _useDispatch2.invalidateResolution;
|
|
162
160
|
var actions = headerData.actions,
|
|
163
161
|
backNav = headerData.backNav,
|
|
164
162
|
badges = headerData.badges,
|
|
@@ -187,30 +185,6 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
187
185
|
_useState2 = _slicedToArray(_useState, 2),
|
|
188
186
|
pluginRequirementsSatisfied = _useState2[0],
|
|
189
187
|
setPluginRequirementsSatisfied = _useState2[1];
|
|
190
|
-
|
|
191
|
-
// The data fetch above runs once per mount, while the required plugins are
|
|
192
|
-
// still missing — endpoints that need them answer empty or error outright,
|
|
193
|
-
// and either way the resolver records that as the answer. So the installer
|
|
194
|
-
// has to trigger a refetch, or the section mounts against it and renders
|
|
195
|
-
// nothing until the user saves. Requirements already met on mount are left
|
|
196
|
-
// alone: that fetch saw the real site, and refetching would cost every such
|
|
197
|
-
// wizard a second request on every load.
|
|
198
|
-
var requirementsWereUnmet = useRef(false);
|
|
199
|
-
var onPluginStatus = function onPluginStatus(_ref3) {
|
|
200
|
-
var complete = _ref3.complete;
|
|
201
|
-
// Leave the installer first: whatever happens to the refetch, the user
|
|
202
|
-
// should not be stranded on a required-plugins screen for a plugin they
|
|
203
|
-
// just installed.
|
|
204
|
-
setPluginRequirementsSatisfied(complete);
|
|
205
|
-
if (!complete) {
|
|
206
|
-
requirementsWereUnmet.current = true;
|
|
207
|
-
} else if (requirementsWereUnmet.current) {
|
|
208
|
-
requirementsWereUnmet.current = false;
|
|
209
|
-
if (apiSlug && isInitialFetchTriggered) {
|
|
210
|
-
invalidateResolution('getWizardAPIData', [apiSlug]);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
188
|
if (!pluginRequirementsSatisfied) {
|
|
215
189
|
headerText = requiredPlugins.length > 1 ? __('Required plugins', 'newspack-plugin') : __('Required plugin', 'newspack-plugin');
|
|
216
190
|
displayedSections = [{
|
|
@@ -218,7 +192,10 @@ var Wizard = function Wizard(_ref2, ref) {
|
|
|
218
192
|
render: function render() {
|
|
219
193
|
return /*#__PURE__*/_jsx(PluginInstaller, {
|
|
220
194
|
plugins: requiredPlugins,
|
|
221
|
-
onStatus:
|
|
195
|
+
onStatus: function onStatus(_ref3) {
|
|
196
|
+
var complete = _ref3.complete;
|
|
197
|
+
return setPluginRequirementsSatisfied(complete);
|
|
198
|
+
}
|
|
222
199
|
});
|
|
223
200
|
}
|
|
224
201
|
}];
|
package/package.json
CHANGED
package/src/grid/style.scss
CHANGED
|
@@ -83,11 +83,13 @@
|
|
|
83
83
|
&__columns-4 {
|
|
84
84
|
grid-template-columns: repeat(2, 1fr);
|
|
85
85
|
|
|
86
|
-
> [start]
|
|
86
|
+
> [start],
|
|
87
|
+
> [data-start] {
|
|
87
88
|
grid-column-start: 1;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
> [end]
|
|
91
|
+
> [end],
|
|
92
|
+
> [data-end] {
|
|
91
93
|
grid-column-end: -1;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
@@ -108,11 +110,26 @@
|
|
|
108
110
|
&__columns-4 {
|
|
109
111
|
grid-template-columns: repeat(4, 1fr);
|
|
110
112
|
|
|
111
|
-
|
|
113
|
+
// Positioning is keyed off `data-` attributes because the emotion-styled
|
|
114
|
+
// components in newer `@wordpress/components` filter props through
|
|
115
|
+
// `@emotion/is-prop-valid` before they reach the DOM. Its allowlist
|
|
116
|
+
// carries `start` (a real attribute, on `<ol>`) but not `end`, so a
|
|
117
|
+
// `VStack` asking for `end` silently lost its `grid-column-end` and
|
|
118
|
+
// collapsed to one column (NEWS-2866). `data-` attributes are never
|
|
119
|
+
// filtered, so they hold whatever component renders them.
|
|
120
|
+
//
|
|
121
|
+
// This is a component-level filter, not a WordPress-wide one: a plain
|
|
122
|
+
// `<div start="2" end="4">` still emits both attributes. The bare
|
|
123
|
+
// selectors below are kept for those plain children — they do NOT
|
|
124
|
+
// rescue an emotion-styled consumer still passing `end`, which never
|
|
125
|
+
// reaches the DOM to be matched.
|
|
126
|
+
> [start="2"],
|
|
127
|
+
> [data-start="2"] {
|
|
112
128
|
grid-column-start: 2;
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
> [end="4"]
|
|
131
|
+
> [end="4"],
|
|
132
|
+
> [data-end="4"] {
|
|
116
133
|
grid-column-end: 4;
|
|
117
134
|
}
|
|
118
135
|
}
|
package/src/wizard/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import classnames from 'classnames';
|
|
|
8
8
|
*/
|
|
9
9
|
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
|
|
10
10
|
import { useDispatch, useSelect } from '@wordpress/data';
|
|
11
|
-
import { cloneElement, isValidElement, useEffect,
|
|
11
|
+
import { cloneElement, isValidElement, useEffect, useState, forwardRef } from '@wordpress/element';
|
|
12
12
|
import { __ } from '@wordpress/i18n';
|
|
13
13
|
import { category, chevronLeft, moreVertical } from '@wordpress/icons';
|
|
14
14
|
|
|
@@ -126,7 +126,6 @@ const Wizard = (
|
|
|
126
126
|
const isQuietLoading = useSelect( select => select( WIZARD_STORE_NAMESPACE ).isQuietLoading() );
|
|
127
127
|
const headerData = useSelect( select => select( WIZARD_STORE_NAMESPACE ).getHeaderData() );
|
|
128
128
|
const notices = useSelect( select => select( WIZARD_STORE_NAMESPACE ).getNotices() );
|
|
129
|
-
const { invalidateResolution } = useDispatch( WIZARD_STORE_NAMESPACE );
|
|
130
129
|
const { actions, backNav, badges, sectionDescription, sectionMenu, sectionName, sectionTitle, sectionPrimaryAction, sectionSecondaryAction } =
|
|
131
130
|
headerData;
|
|
132
131
|
|
|
@@ -140,36 +139,14 @@ const Wizard = (
|
|
|
140
139
|
let displayedSections = sections.filter( section => ! section.isHidden );
|
|
141
140
|
|
|
142
141
|
const [ pluginRequirementsSatisfied, setPluginRequirementsSatisfied ] = useState( requiredPlugins.length === 0 );
|
|
143
|
-
|
|
144
|
-
// The data fetch above runs once per mount, while the required plugins are
|
|
145
|
-
// still missing — endpoints that need them answer empty or error outright,
|
|
146
|
-
// and either way the resolver records that as the answer. So the installer
|
|
147
|
-
// has to trigger a refetch, or the section mounts against it and renders
|
|
148
|
-
// nothing until the user saves. Requirements already met on mount are left
|
|
149
|
-
// alone: that fetch saw the real site, and refetching would cost every such
|
|
150
|
-
// wizard a second request on every load.
|
|
151
|
-
const requirementsWereUnmet = useRef( false );
|
|
152
|
-
const onPluginStatus = ( { complete } ) => {
|
|
153
|
-
// Leave the installer first: whatever happens to the refetch, the user
|
|
154
|
-
// should not be stranded on a required-plugins screen for a plugin they
|
|
155
|
-
// just installed.
|
|
156
|
-
setPluginRequirementsSatisfied( complete );
|
|
157
|
-
if ( ! complete ) {
|
|
158
|
-
requirementsWereUnmet.current = true;
|
|
159
|
-
} else if ( requirementsWereUnmet.current ) {
|
|
160
|
-
requirementsWereUnmet.current = false;
|
|
161
|
-
if ( apiSlug && isInitialFetchTriggered ) {
|
|
162
|
-
invalidateResolution( 'getWizardAPIData', [ apiSlug ] );
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
142
|
if ( ! pluginRequirementsSatisfied ) {
|
|
168
143
|
headerText = requiredPlugins.length > 1 ? __( 'Required plugins', 'newspack-plugin' ) : __( 'Required plugin', 'newspack-plugin' );
|
|
169
144
|
displayedSections = [
|
|
170
145
|
{
|
|
171
146
|
path: '/',
|
|
172
|
-
render: () =>
|
|
147
|
+
render: () => (
|
|
148
|
+
<PluginInstaller plugins={ requiredPlugins } onStatus={ ( { complete } ) => setPluginRequirementsSatisfied( complete ) } />
|
|
149
|
+
),
|
|
173
150
|
},
|
|
174
151
|
];
|
|
175
152
|
}
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
-
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
5
|
-
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
6
|
-
var _react = require("@testing-library/react");
|
|
7
|
-
var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch"));
|
|
8
|
-
var _ = _interopRequireDefault(require("./"));
|
|
9
|
-
var _utils = require("./store/utils");
|
|
10
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
-
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
|
-
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); } /**
|
|
13
|
-
* External dependencies.
|
|
14
|
-
*/ /**
|
|
15
|
-
* WordPress dependencies.
|
|
16
|
-
*/ /**
|
|
17
|
-
* Internal dependencies.
|
|
18
|
-
*/
|
|
19
|
-
jest.mock('@wordpress/api-fetch', function () {
|
|
20
|
-
return jest.fn();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Both globals are localized onto every real wizard screen. The footer's
|
|
24
|
-
// ExternalLink needs a string href, and the debug Notice reads aux data.
|
|
25
|
-
window.newspack_aux_data = {
|
|
26
|
-
is_debug_mode: false
|
|
27
|
-
};
|
|
28
|
-
window.newspack_urls = {
|
|
29
|
-
support: 'https://help.newspack.com/'
|
|
30
|
-
};
|
|
31
|
-
var SETTINGS = {
|
|
32
|
-
minimumDonation: '5'
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Stands in for a wizard section that can only render once its API data has
|
|
36
|
-
// arrived — the shape a wizard section has when the wizard owns an apiSlug.
|
|
37
|
-
var Section = function Section(_ref) {
|
|
38
|
-
var slug = _ref.slug;
|
|
39
|
-
var wizardData = (0, _utils.useWizardData)(slug);
|
|
40
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
41
|
-
children: wizardData.settings ? 'Settings form' : null
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// The other shape: a section on a wizard with no apiSlug, which reads page-load
|
|
46
|
-
// globals rather than wizard API data and so never touches the store.
|
|
47
|
-
var StaticSection = function StaticSection() {
|
|
48
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
|
|
49
|
-
children: "Static section"
|
|
50
|
-
});
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Mocks the endpoints a wizard hits, gated on whether the required plugin is
|
|
54
|
-
// active: the wizard's own endpoint 400s while the plugin is missing, exactly as
|
|
55
|
-
// Audience_Donations::api_get_donation_settings() does without WooCommerce.
|
|
56
|
-
// Counts every wizard-endpoint GET, whatever the slug, so a test can assert that
|
|
57
|
-
// a wizard given no apiSlug fetches nothing at all.
|
|
58
|
-
var mockEndpoints = function mockEndpoints(slug) {
|
|
59
|
-
var initialStatus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'inactive';
|
|
60
|
-
var counts = {
|
|
61
|
-
wizard: 0
|
|
62
|
-
};
|
|
63
|
-
var pluginStatus = initialStatus;
|
|
64
|
-
var plugin = function plugin() {
|
|
65
|
-
return {
|
|
66
|
-
Name: 'WooCommerce',
|
|
67
|
-
Description: 'Store',
|
|
68
|
-
Status: pluginStatus,
|
|
69
|
-
Download: 'wporg'
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
_apiFetch["default"].mockImplementation(function (_ref2) {
|
|
73
|
-
var path = _ref2.path,
|
|
74
|
-
method = _ref2.method;
|
|
75
|
-
if (path.startsWith('/newspack/v1/wizard/') && 'POST' !== method) {
|
|
76
|
-
counts.wizard++;
|
|
77
|
-
return 'active' === pluginStatus ? Promise.resolve({
|
|
78
|
-
settings: SETTINGS
|
|
79
|
-
}) : Promise.reject({
|
|
80
|
-
code: 'newspack_missing_required_plugin',
|
|
81
|
-
message: 'The WooCommerce plugin is not installed and activated.'
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
if (path === '/newspack/v1/plugins/') {
|
|
85
|
-
return Promise.resolve({
|
|
86
|
-
woocommerce: plugin()
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (path === '/newspack/v1/plugins/woocommerce/configure/') {
|
|
90
|
-
pluginStatus = 'active';
|
|
91
|
-
return Promise.resolve(plugin());
|
|
92
|
-
}
|
|
93
|
-
return Promise.resolve({});
|
|
94
|
-
});
|
|
95
|
-
return counts;
|
|
96
|
-
};
|
|
97
|
-
var renderWizard = function renderWizard(slug) {
|
|
98
|
-
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
99
|
-
_ref3$withApiSlug = _ref3.withApiSlug,
|
|
100
|
-
withApiSlug = _ref3$withApiSlug === void 0 ? true : _ref3$withApiSlug;
|
|
101
|
-
return (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_["default"], {
|
|
102
|
-
headerText: "Test wizard",
|
|
103
|
-
apiSlug: withApiSlug ? slug : undefined,
|
|
104
|
-
requiredPlugins: ['woocommerce'],
|
|
105
|
-
sections: [{
|
|
106
|
-
label: 'Configuration',
|
|
107
|
-
path: '/configuration',
|
|
108
|
-
render: function render() {
|
|
109
|
-
return withApiSlug ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Section, {
|
|
110
|
-
slug: slug
|
|
111
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(StaticSection, {});
|
|
112
|
-
}
|
|
113
|
-
}]
|
|
114
|
-
}));
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// The plugin's own row button, not the footer's activate-all — both read
|
|
118
|
-
// "Activate", and the row button comes first in the DOM.
|
|
119
|
-
var clickActivate = /*#__PURE__*/function () {
|
|
120
|
-
var _ref4 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
121
|
-
var _yield$screen$findAll, _yield$screen$findAll2, activate;
|
|
122
|
-
return _regenerator().w(function (_context) {
|
|
123
|
-
while (1) switch (_context.n) {
|
|
124
|
-
case 0:
|
|
125
|
-
_context.n = 1;
|
|
126
|
-
return _react.screen.findAllByRole('button', {
|
|
127
|
-
name: 'Activate'
|
|
128
|
-
});
|
|
129
|
-
case 1:
|
|
130
|
-
_yield$screen$findAll = _context.v;
|
|
131
|
-
_yield$screen$findAll2 = (0, _slicedToArray2["default"])(_yield$screen$findAll, 1);
|
|
132
|
-
activate = _yield$screen$findAll2[0];
|
|
133
|
-
_react.fireEvent.click(activate);
|
|
134
|
-
case 2:
|
|
135
|
-
return _context.a(2);
|
|
136
|
-
}
|
|
137
|
-
}, _callee);
|
|
138
|
-
}));
|
|
139
|
-
return function clickActivate() {
|
|
140
|
-
return _ref4.apply(this, arguments);
|
|
141
|
-
};
|
|
142
|
-
}();
|
|
143
|
-
|
|
144
|
-
// Lets every already-queued promise settle, so an assertion that something did
|
|
145
|
-
// NOT happen has actually given it the chance to.
|
|
146
|
-
var flushPending = function flushPending() {
|
|
147
|
-
return (0, _react.act)(function () {
|
|
148
|
-
return new Promise(function (resolve) {
|
|
149
|
-
return setTimeout(resolve, 0);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
describe('Wizard', function () {
|
|
154
|
-
beforeEach(function () {
|
|
155
|
-
_apiFetch["default"].mockReset();
|
|
156
|
-
});
|
|
157
|
-
it('refetches its API data after the installer satisfies the plugin requirements', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
158
|
-
var slug, counts, _t, _t2;
|
|
159
|
-
return _regenerator().w(function (_context2) {
|
|
160
|
-
while (1) switch (_context2.n) {
|
|
161
|
-
case 0:
|
|
162
|
-
// A distinct slug per test: the store's resolution cache lives in the
|
|
163
|
-
// module registry, so it is shared by every test in this file.
|
|
164
|
-
slug = 'test-wizard-refetch';
|
|
165
|
-
counts = mockEndpoints(slug);
|
|
166
|
-
renderWizard(slug);
|
|
167
|
-
|
|
168
|
-
// The first fetch runs before the plugin exists, so it fails. Until the
|
|
169
|
-
// installer is satisfied it owns the whole route, so the installer screen
|
|
170
|
-
// showing is what proves we are in the pre-activation state.
|
|
171
|
-
_context2.n = 1;
|
|
172
|
-
return (0, _react.waitFor)(function () {
|
|
173
|
-
return expect(counts.wizard).toBe(1);
|
|
174
|
-
});
|
|
175
|
-
case 1:
|
|
176
|
-
_t = expect;
|
|
177
|
-
_context2.n = 2;
|
|
178
|
-
return _react.screen.findByText('WooCommerce');
|
|
179
|
-
case 2:
|
|
180
|
-
_t(_context2.v).toBeInTheDocument();
|
|
181
|
-
_context2.n = 3;
|
|
182
|
-
return clickActivate();
|
|
183
|
-
case 3:
|
|
184
|
-
_t2 = expect;
|
|
185
|
-
_context2.n = 4;
|
|
186
|
-
return _react.screen.findByText('Settings form');
|
|
187
|
-
case 4:
|
|
188
|
-
_t2(_context2.v).toBeInTheDocument();
|
|
189
|
-
_context2.n = 5;
|
|
190
|
-
return flushPending();
|
|
191
|
-
case 5:
|
|
192
|
-
expect(counts.wizard).toBe(2);
|
|
193
|
-
case 6:
|
|
194
|
-
return _context2.a(2);
|
|
195
|
-
}
|
|
196
|
-
}, _callee2);
|
|
197
|
-
})));
|
|
198
|
-
it('does not refetch when the required plugins were already active', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee3() {
|
|
199
|
-
var slug, counts, _t3;
|
|
200
|
-
return _regenerator().w(function (_context3) {
|
|
201
|
-
while (1) switch (_context3.n) {
|
|
202
|
-
case 0:
|
|
203
|
-
slug = 'test-wizard-no-refetch';
|
|
204
|
-
counts = mockEndpoints(slug, 'active');
|
|
205
|
-
renderWizard(slug);
|
|
206
|
-
_t3 = expect;
|
|
207
|
-
_context3.n = 1;
|
|
208
|
-
return _react.screen.findByText('Settings form');
|
|
209
|
-
case 1:
|
|
210
|
-
_t3(_context3.v).toBeInTheDocument();
|
|
211
|
-
_context3.n = 2;
|
|
212
|
-
return flushPending();
|
|
213
|
-
case 2:
|
|
214
|
-
expect(counts.wizard).toBe(1);
|
|
215
|
-
case 3:
|
|
216
|
-
return _context3.a(2);
|
|
217
|
-
}
|
|
218
|
-
}, _callee3);
|
|
219
|
-
})));
|
|
220
|
-
it('fetches nothing for a wizard that has required plugins but no API slug', /*#__PURE__*/(0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator().m(function _callee4() {
|
|
221
|
-
var slug, counts, _t4;
|
|
222
|
-
return _regenerator().w(function (_context4) {
|
|
223
|
-
while (1) switch (_context4.n) {
|
|
224
|
-
case 0:
|
|
225
|
-
// The shape of every other Newspack wizard that gates on plugins: it
|
|
226
|
-
// reads page-load globals rather than wizard API data. This change has to
|
|
227
|
-
// stay inert for them. Note this pins the inertness, not the `apiSlug`
|
|
228
|
-
// guard itself — the resolver already no-ops on an undefined slug, so
|
|
229
|
-
// that guard is a second line of defence rather than the load-bearing one.
|
|
230
|
-
slug = 'test-wizard-no-api-slug';
|
|
231
|
-
counts = mockEndpoints(slug);
|
|
232
|
-
renderWizard(slug, {
|
|
233
|
-
withApiSlug: false
|
|
234
|
-
});
|
|
235
|
-
_context4.n = 1;
|
|
236
|
-
return clickActivate();
|
|
237
|
-
case 1:
|
|
238
|
-
_t4 = expect;
|
|
239
|
-
_context4.n = 2;
|
|
240
|
-
return _react.screen.findByText('Static section');
|
|
241
|
-
case 2:
|
|
242
|
-
_t4(_context4.v).toBeInTheDocument();
|
|
243
|
-
_context4.n = 3;
|
|
244
|
-
return flushPending();
|
|
245
|
-
case 3:
|
|
246
|
-
expect(counts.wizard).toBe(0);
|
|
247
|
-
case 4:
|
|
248
|
-
return _context4.a(2);
|
|
249
|
-
}
|
|
250
|
-
}, _callee4);
|
|
251
|
-
})));
|
|
252
|
-
});
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
-
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
3
|
-
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 }; })(); }
|
|
4
|
-
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); }
|
|
5
|
-
/**
|
|
6
|
-
* External dependencies.
|
|
7
|
-
*/
|
|
8
|
-
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* WordPress dependencies.
|
|
12
|
-
*/
|
|
13
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Internal dependencies.
|
|
17
|
-
*/
|
|
18
|
-
import Wizard from './';
|
|
19
|
-
import { useWizardData } from './store/utils';
|
|
20
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
21
|
-
jest.mock('@wordpress/api-fetch', function () {
|
|
22
|
-
return jest.fn();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// Both globals are localized onto every real wizard screen. The footer's
|
|
26
|
-
// ExternalLink needs a string href, and the debug Notice reads aux data.
|
|
27
|
-
window.newspack_aux_data = {
|
|
28
|
-
is_debug_mode: false
|
|
29
|
-
};
|
|
30
|
-
window.newspack_urls = {
|
|
31
|
-
support: 'https://help.newspack.com/'
|
|
32
|
-
};
|
|
33
|
-
var SETTINGS = {
|
|
34
|
-
minimumDonation: '5'
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Stands in for a wizard section that can only render once its API data has
|
|
38
|
-
// arrived — the shape a wizard section has when the wizard owns an apiSlug.
|
|
39
|
-
var Section = function Section(_ref) {
|
|
40
|
-
var slug = _ref.slug;
|
|
41
|
-
var wizardData = useWizardData(slug);
|
|
42
|
-
return /*#__PURE__*/_jsx("div", {
|
|
43
|
-
children: wizardData.settings ? 'Settings form' : null
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// The other shape: a section on a wizard with no apiSlug, which reads page-load
|
|
48
|
-
// globals rather than wizard API data and so never touches the store.
|
|
49
|
-
var StaticSection = function StaticSection() {
|
|
50
|
-
return /*#__PURE__*/_jsx("div", {
|
|
51
|
-
children: "Static section"
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Mocks the endpoints a wizard hits, gated on whether the required plugin is
|
|
56
|
-
// active: the wizard's own endpoint 400s while the plugin is missing, exactly as
|
|
57
|
-
// Audience_Donations::api_get_donation_settings() does without WooCommerce.
|
|
58
|
-
// Counts every wizard-endpoint GET, whatever the slug, so a test can assert that
|
|
59
|
-
// a wizard given no apiSlug fetches nothing at all.
|
|
60
|
-
var mockEndpoints = function mockEndpoints(slug) {
|
|
61
|
-
var initialStatus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'inactive';
|
|
62
|
-
var counts = {
|
|
63
|
-
wizard: 0
|
|
64
|
-
};
|
|
65
|
-
var pluginStatus = initialStatus;
|
|
66
|
-
var plugin = function plugin() {
|
|
67
|
-
return {
|
|
68
|
-
Name: 'WooCommerce',
|
|
69
|
-
Description: 'Store',
|
|
70
|
-
Status: pluginStatus,
|
|
71
|
-
Download: 'wporg'
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
apiFetch.mockImplementation(function (_ref2) {
|
|
75
|
-
var path = _ref2.path,
|
|
76
|
-
method = _ref2.method;
|
|
77
|
-
if (path.startsWith('/newspack/v1/wizard/') && 'POST' !== method) {
|
|
78
|
-
counts.wizard++;
|
|
79
|
-
return 'active' === pluginStatus ? Promise.resolve({
|
|
80
|
-
settings: SETTINGS
|
|
81
|
-
}) : Promise.reject({
|
|
82
|
-
code: 'newspack_missing_required_plugin',
|
|
83
|
-
message: 'The WooCommerce plugin is not installed and activated.'
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
if (path === '/newspack/v1/plugins/') {
|
|
87
|
-
return Promise.resolve({
|
|
88
|
-
woocommerce: plugin()
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (path === '/newspack/v1/plugins/woocommerce/configure/') {
|
|
92
|
-
pluginStatus = 'active';
|
|
93
|
-
return Promise.resolve(plugin());
|
|
94
|
-
}
|
|
95
|
-
return Promise.resolve({});
|
|
96
|
-
});
|
|
97
|
-
return counts;
|
|
98
|
-
};
|
|
99
|
-
var renderWizard = function renderWizard(slug) {
|
|
100
|
-
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
101
|
-
_ref3$withApiSlug = _ref3.withApiSlug,
|
|
102
|
-
withApiSlug = _ref3$withApiSlug === void 0 ? true : _ref3$withApiSlug;
|
|
103
|
-
return render(/*#__PURE__*/_jsx(Wizard, {
|
|
104
|
-
headerText: "Test wizard",
|
|
105
|
-
apiSlug: withApiSlug ? slug : undefined,
|
|
106
|
-
requiredPlugins: ['woocommerce'],
|
|
107
|
-
sections: [{
|
|
108
|
-
label: 'Configuration',
|
|
109
|
-
path: '/configuration',
|
|
110
|
-
render: function render() {
|
|
111
|
-
return withApiSlug ? /*#__PURE__*/_jsx(Section, {
|
|
112
|
-
slug: slug
|
|
113
|
-
}) : /*#__PURE__*/_jsx(StaticSection, {});
|
|
114
|
-
}
|
|
115
|
-
}]
|
|
116
|
-
}));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// The plugin's own row button, not the footer's activate-all — both read
|
|
120
|
-
// "Activate", and the row button comes first in the DOM.
|
|
121
|
-
var clickActivate = /*#__PURE__*/function () {
|
|
122
|
-
var _ref4 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
123
|
-
var _yield$screen$findAll, _yield$screen$findAll2, activate;
|
|
124
|
-
return _regenerator().w(function (_context) {
|
|
125
|
-
while (1) switch (_context.n) {
|
|
126
|
-
case 0:
|
|
127
|
-
_context.n = 1;
|
|
128
|
-
return screen.findAllByRole('button', {
|
|
129
|
-
name: 'Activate'
|
|
130
|
-
});
|
|
131
|
-
case 1:
|
|
132
|
-
_yield$screen$findAll = _context.v;
|
|
133
|
-
_yield$screen$findAll2 = _slicedToArray(_yield$screen$findAll, 1);
|
|
134
|
-
activate = _yield$screen$findAll2[0];
|
|
135
|
-
fireEvent.click(activate);
|
|
136
|
-
case 2:
|
|
137
|
-
return _context.a(2);
|
|
138
|
-
}
|
|
139
|
-
}, _callee);
|
|
140
|
-
}));
|
|
141
|
-
return function clickActivate() {
|
|
142
|
-
return _ref4.apply(this, arguments);
|
|
143
|
-
};
|
|
144
|
-
}();
|
|
145
|
-
|
|
146
|
-
// Lets every already-queued promise settle, so an assertion that something did
|
|
147
|
-
// NOT happen has actually given it the chance to.
|
|
148
|
-
var flushPending = function flushPending() {
|
|
149
|
-
return act(function () {
|
|
150
|
-
return new Promise(function (resolve) {
|
|
151
|
-
return setTimeout(resolve, 0);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
};
|
|
155
|
-
describe('Wizard', function () {
|
|
156
|
-
beforeEach(function () {
|
|
157
|
-
apiFetch.mockReset();
|
|
158
|
-
});
|
|
159
|
-
it('refetches its API data after the installer satisfies the plugin requirements', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
160
|
-
var slug, counts, _t, _t2;
|
|
161
|
-
return _regenerator().w(function (_context2) {
|
|
162
|
-
while (1) switch (_context2.n) {
|
|
163
|
-
case 0:
|
|
164
|
-
// A distinct slug per test: the store's resolution cache lives in the
|
|
165
|
-
// module registry, so it is shared by every test in this file.
|
|
166
|
-
slug = 'test-wizard-refetch';
|
|
167
|
-
counts = mockEndpoints(slug);
|
|
168
|
-
renderWizard(slug);
|
|
169
|
-
|
|
170
|
-
// The first fetch runs before the plugin exists, so it fails. Until the
|
|
171
|
-
// installer is satisfied it owns the whole route, so the installer screen
|
|
172
|
-
// showing is what proves we are in the pre-activation state.
|
|
173
|
-
_context2.n = 1;
|
|
174
|
-
return waitFor(function () {
|
|
175
|
-
return expect(counts.wizard).toBe(1);
|
|
176
|
-
});
|
|
177
|
-
case 1:
|
|
178
|
-
_t = expect;
|
|
179
|
-
_context2.n = 2;
|
|
180
|
-
return screen.findByText('WooCommerce');
|
|
181
|
-
case 2:
|
|
182
|
-
_t(_context2.v).toBeInTheDocument();
|
|
183
|
-
_context2.n = 3;
|
|
184
|
-
return clickActivate();
|
|
185
|
-
case 3:
|
|
186
|
-
_t2 = expect;
|
|
187
|
-
_context2.n = 4;
|
|
188
|
-
return screen.findByText('Settings form');
|
|
189
|
-
case 4:
|
|
190
|
-
_t2(_context2.v).toBeInTheDocument();
|
|
191
|
-
_context2.n = 5;
|
|
192
|
-
return flushPending();
|
|
193
|
-
case 5:
|
|
194
|
-
expect(counts.wizard).toBe(2);
|
|
195
|
-
case 6:
|
|
196
|
-
return _context2.a(2);
|
|
197
|
-
}
|
|
198
|
-
}, _callee2);
|
|
199
|
-
})));
|
|
200
|
-
it('does not refetch when the required plugins were already active', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
|
|
201
|
-
var slug, counts, _t3;
|
|
202
|
-
return _regenerator().w(function (_context3) {
|
|
203
|
-
while (1) switch (_context3.n) {
|
|
204
|
-
case 0:
|
|
205
|
-
slug = 'test-wizard-no-refetch';
|
|
206
|
-
counts = mockEndpoints(slug, 'active');
|
|
207
|
-
renderWizard(slug);
|
|
208
|
-
_t3 = expect;
|
|
209
|
-
_context3.n = 1;
|
|
210
|
-
return screen.findByText('Settings form');
|
|
211
|
-
case 1:
|
|
212
|
-
_t3(_context3.v).toBeInTheDocument();
|
|
213
|
-
_context3.n = 2;
|
|
214
|
-
return flushPending();
|
|
215
|
-
case 2:
|
|
216
|
-
expect(counts.wizard).toBe(1);
|
|
217
|
-
case 3:
|
|
218
|
-
return _context3.a(2);
|
|
219
|
-
}
|
|
220
|
-
}, _callee3);
|
|
221
|
-
})));
|
|
222
|
-
it('fetches nothing for a wizard that has required plugins but no API slug', /*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4() {
|
|
223
|
-
var slug, counts, _t4;
|
|
224
|
-
return _regenerator().w(function (_context4) {
|
|
225
|
-
while (1) switch (_context4.n) {
|
|
226
|
-
case 0:
|
|
227
|
-
// The shape of every other Newspack wizard that gates on plugins: it
|
|
228
|
-
// reads page-load globals rather than wizard API data. This change has to
|
|
229
|
-
// stay inert for them. Note this pins the inertness, not the `apiSlug`
|
|
230
|
-
// guard itself — the resolver already no-ops on an undefined slug, so
|
|
231
|
-
// that guard is a second line of defence rather than the load-bearing one.
|
|
232
|
-
slug = 'test-wizard-no-api-slug';
|
|
233
|
-
counts = mockEndpoints(slug);
|
|
234
|
-
renderWizard(slug, {
|
|
235
|
-
withApiSlug: false
|
|
236
|
-
});
|
|
237
|
-
_context4.n = 1;
|
|
238
|
-
return clickActivate();
|
|
239
|
-
case 1:
|
|
240
|
-
_t4 = expect;
|
|
241
|
-
_context4.n = 2;
|
|
242
|
-
return screen.findByText('Static section');
|
|
243
|
-
case 2:
|
|
244
|
-
_t4(_context4.v).toBeInTheDocument();
|
|
245
|
-
_context4.n = 3;
|
|
246
|
-
return flushPending();
|
|
247
|
-
case 3:
|
|
248
|
-
expect(counts.wizard).toBe(0);
|
|
249
|
-
case 4:
|
|
250
|
-
return _context4.a(2);
|
|
251
|
-
}
|
|
252
|
-
}, _callee4);
|
|
253
|
-
})));
|
|
254
|
-
});
|
package/src/wizard/index.test.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies.
|
|
3
|
-
*/
|
|
4
|
-
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* WordPress dependencies.
|
|
8
|
-
*/
|
|
9
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Internal dependencies.
|
|
13
|
-
*/
|
|
14
|
-
import Wizard from './';
|
|
15
|
-
import { useWizardData } from './store/utils';
|
|
16
|
-
|
|
17
|
-
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
|
18
|
-
|
|
19
|
-
// Both globals are localized onto every real wizard screen. The footer's
|
|
20
|
-
// ExternalLink needs a string href, and the debug Notice reads aux data.
|
|
21
|
-
window.newspack_aux_data = { is_debug_mode: false };
|
|
22
|
-
window.newspack_urls = { support: 'https://help.newspack.com/' };
|
|
23
|
-
|
|
24
|
-
const SETTINGS = { minimumDonation: '5' };
|
|
25
|
-
|
|
26
|
-
// Stands in for a wizard section that can only render once its API data has
|
|
27
|
-
// arrived — the shape a wizard section has when the wizard owns an apiSlug.
|
|
28
|
-
const Section = ( { slug } ) => {
|
|
29
|
-
const wizardData = useWizardData( slug );
|
|
30
|
-
return <div>{ wizardData.settings ? 'Settings form' : null }</div>;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// The other shape: a section on a wizard with no apiSlug, which reads page-load
|
|
34
|
-
// globals rather than wizard API data and so never touches the store.
|
|
35
|
-
const StaticSection = () => <div>Static section</div>;
|
|
36
|
-
|
|
37
|
-
// Mocks the endpoints a wizard hits, gated on whether the required plugin is
|
|
38
|
-
// active: the wizard's own endpoint 400s while the plugin is missing, exactly as
|
|
39
|
-
// Audience_Donations::api_get_donation_settings() does without WooCommerce.
|
|
40
|
-
// Counts every wizard-endpoint GET, whatever the slug, so a test can assert that
|
|
41
|
-
// a wizard given no apiSlug fetches nothing at all.
|
|
42
|
-
const mockEndpoints = ( slug, initialStatus = 'inactive' ) => {
|
|
43
|
-
const counts = { wizard: 0 };
|
|
44
|
-
let pluginStatus = initialStatus;
|
|
45
|
-
const plugin = () => ( { Name: 'WooCommerce', Description: 'Store', Status: pluginStatus, Download: 'wporg' } );
|
|
46
|
-
apiFetch.mockImplementation( ( { path, method } ) => {
|
|
47
|
-
if ( path.startsWith( '/newspack/v1/wizard/' ) && 'POST' !== method ) {
|
|
48
|
-
counts.wizard++;
|
|
49
|
-
return 'active' === pluginStatus
|
|
50
|
-
? Promise.resolve( { settings: SETTINGS } )
|
|
51
|
-
: Promise.reject( {
|
|
52
|
-
code: 'newspack_missing_required_plugin',
|
|
53
|
-
message: 'The WooCommerce plugin is not installed and activated.',
|
|
54
|
-
} );
|
|
55
|
-
}
|
|
56
|
-
if ( path === '/newspack/v1/plugins/' ) {
|
|
57
|
-
return Promise.resolve( { woocommerce: plugin() } );
|
|
58
|
-
}
|
|
59
|
-
if ( path === '/newspack/v1/plugins/woocommerce/configure/' ) {
|
|
60
|
-
pluginStatus = 'active';
|
|
61
|
-
return Promise.resolve( plugin() );
|
|
62
|
-
}
|
|
63
|
-
return Promise.resolve( {} );
|
|
64
|
-
} );
|
|
65
|
-
return counts;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const renderWizard = ( slug, { withApiSlug = true } = {} ) =>
|
|
69
|
-
render(
|
|
70
|
-
<Wizard
|
|
71
|
-
headerText="Test wizard"
|
|
72
|
-
apiSlug={ withApiSlug ? slug : undefined }
|
|
73
|
-
requiredPlugins={ [ 'woocommerce' ] }
|
|
74
|
-
sections={ [
|
|
75
|
-
{
|
|
76
|
-
label: 'Configuration',
|
|
77
|
-
path: '/configuration',
|
|
78
|
-
render: () => ( withApiSlug ? <Section slug={ slug } /> : <StaticSection /> ),
|
|
79
|
-
},
|
|
80
|
-
] }
|
|
81
|
-
/>
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// The plugin's own row button, not the footer's activate-all — both read
|
|
85
|
-
// "Activate", and the row button comes first in the DOM.
|
|
86
|
-
const clickActivate = async () => {
|
|
87
|
-
const [ activate ] = await screen.findAllByRole( 'button', { name: 'Activate' } );
|
|
88
|
-
fireEvent.click( activate );
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Lets every already-queued promise settle, so an assertion that something did
|
|
92
|
-
// NOT happen has actually given it the chance to.
|
|
93
|
-
const flushPending = () => act( () => new Promise( resolve => setTimeout( resolve, 0 ) ) );
|
|
94
|
-
|
|
95
|
-
describe( 'Wizard', () => {
|
|
96
|
-
beforeEach( () => {
|
|
97
|
-
apiFetch.mockReset();
|
|
98
|
-
} );
|
|
99
|
-
|
|
100
|
-
it( 'refetches its API data after the installer satisfies the plugin requirements', async () => {
|
|
101
|
-
// A distinct slug per test: the store's resolution cache lives in the
|
|
102
|
-
// module registry, so it is shared by every test in this file.
|
|
103
|
-
const slug = 'test-wizard-refetch';
|
|
104
|
-
const counts = mockEndpoints( slug );
|
|
105
|
-
|
|
106
|
-
renderWizard( slug );
|
|
107
|
-
|
|
108
|
-
// The first fetch runs before the plugin exists, so it fails. Until the
|
|
109
|
-
// installer is satisfied it owns the whole route, so the installer screen
|
|
110
|
-
// showing is what proves we are in the pre-activation state.
|
|
111
|
-
await waitFor( () => expect( counts.wizard ).toBe( 1 ) );
|
|
112
|
-
expect( await screen.findByText( 'WooCommerce' ) ).toBeInTheDocument();
|
|
113
|
-
|
|
114
|
-
await clickActivate();
|
|
115
|
-
|
|
116
|
-
// Activation makes the endpoint answerable, so the section must render
|
|
117
|
-
// against fresh data rather than the empty pre-activation response.
|
|
118
|
-
expect( await screen.findByText( 'Settings form' ) ).toBeInTheDocument();
|
|
119
|
-
|
|
120
|
-
// Exactly one refetch. The installer re-reports its status on every
|
|
121
|
-
// re-render while it is mounted, so this is the assertion that would
|
|
122
|
-
// catch a latch that re-fired.
|
|
123
|
-
await flushPending();
|
|
124
|
-
expect( counts.wizard ).toBe( 2 );
|
|
125
|
-
} );
|
|
126
|
-
|
|
127
|
-
it( 'does not refetch when the required plugins were already active', async () => {
|
|
128
|
-
const slug = 'test-wizard-no-refetch';
|
|
129
|
-
const counts = mockEndpoints( slug, 'active' );
|
|
130
|
-
|
|
131
|
-
renderWizard( slug );
|
|
132
|
-
|
|
133
|
-
expect( await screen.findByText( 'Settings form' ) ).toBeInTheDocument();
|
|
134
|
-
await flushPending();
|
|
135
|
-
expect( counts.wizard ).toBe( 1 );
|
|
136
|
-
} );
|
|
137
|
-
|
|
138
|
-
it( 'fetches nothing for a wizard that has required plugins but no API slug', async () => {
|
|
139
|
-
// The shape of every other Newspack wizard that gates on plugins: it
|
|
140
|
-
// reads page-load globals rather than wizard API data. This change has to
|
|
141
|
-
// stay inert for them. Note this pins the inertness, not the `apiSlug`
|
|
142
|
-
// guard itself — the resolver already no-ops on an undefined slug, so
|
|
143
|
-
// that guard is a second line of defence rather than the load-bearing one.
|
|
144
|
-
const slug = 'test-wizard-no-api-slug';
|
|
145
|
-
const counts = mockEndpoints( slug );
|
|
146
|
-
|
|
147
|
-
renderWizard( slug, { withApiSlug: false } );
|
|
148
|
-
|
|
149
|
-
await clickActivate();
|
|
150
|
-
|
|
151
|
-
// The installer still completes and hands the route back to the section.
|
|
152
|
-
expect( await screen.findByText( 'Static section' ) ).toBeInTheDocument();
|
|
153
|
-
await flushPending();
|
|
154
|
-
expect( counts.wizard ).toBe( 0 );
|
|
155
|
-
} );
|
|
156
|
-
} );
|