newspack-components 4.8.0 → 4.9.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 +10 -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/card-feature/index.js +28 -3
- package/dist/cjs/card-feature/index.test.js +100 -45
- package/dist/cjs/card-feature/style.scss +1 -5
- 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/integration-icon/index.js +4 -4
- package/dist/cjs/integration-icon/style.scss +18 -2
- package/dist/cjs/integration-icons/active-campaign.js +4 -7
- package/dist/cjs/integration-icons/beehiiv.js +19 -0
- package/dist/cjs/integration-icons/gravity-forms.js +19 -0
- package/dist/cjs/integration-icons/index.js +26 -6
- package/dist/cjs/integration-icons/salesforce.js +5 -3
- 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/breadcrumbs-select.js +17 -4
- package/dist/cjs/wizard/breadcrumbs-select.test.js +17 -0
- package/dist/cjs/wizard/index.js +50 -13
- package/dist/cjs/wizard/index.test.js +304 -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/card-feature/index.js +29 -4
- package/dist/esm/card-feature/index.test.js +100 -45
- package/dist/esm/card-feature/style.scss +1 -5
- 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/integration-icon/index.js +5 -5
- package/dist/esm/integration-icon/style.scss +18 -2
- package/dist/esm/integration-icons/active-campaign.js +5 -8
- package/dist/esm/integration-icons/beehiiv.js +13 -0
- package/dist/esm/integration-icons/gravity-forms.js +13 -0
- package/dist/esm/integration-icons/index.js +15 -4
- package/dist/esm/integration-icons/salesforce.js +5 -3
- 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/breadcrumbs-select.js +16 -3
- package/dist/esm/wizard/breadcrumbs-select.test.js +18 -1
- package/dist/esm/wizard/index.js +53 -16
- package/dist/esm/wizard/index.test.js +304 -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/card-feature/README.md +10 -11
- package/src/card-feature/index.test.js +45 -15
- package/src/card-feature/index.tsx +43 -24
- package/src/card-feature/style.scss +1 -5
- 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/integration-icon/index.tsx +5 -5
- package/src/integration-icon/style.scss +18 -2
- package/src/integration-icons/active-campaign.js +1 -5
- package/src/integration-icons/beehiiv.js +10 -0
- package/src/integration-icons/gravity-forms.js +10 -0
- package/src/integration-icons/index.js +14 -3
- package/src/integration-icons/salesforce.js +3 -3
- 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/breadcrumbs-select.js +12 -4
- package/src/wizard/breadcrumbs-select.test.js +15 -1
- package/src/wizard/index.js +45 -14
- package/src/wizard/index.test.js +169 -3
- package/src/wizard/store/index.js +2 -0
|
@@ -9,8 +9,6 @@ import { HashRouter } from 'react-router-dom';
|
|
|
9
9
|
*/
|
|
10
10
|
import withWizardScreen from './';
|
|
11
11
|
|
|
12
|
-
global.newspack_aux_data = { is_debug_mode: false };
|
|
13
|
-
|
|
14
12
|
describe( 'withWizardScreen', () => {
|
|
15
13
|
it( 'falls back to a single headerText breadcrumb when no breadcrumbItems are provided', () => {
|
|
16
14
|
const WrappedComponent = () => <div>Body content</div>;
|
|
@@ -11,12 +11,20 @@ import { matchesRoute } from '../route-match';
|
|
|
11
11
|
* @param {string} pathname Current router pathname.
|
|
12
12
|
* @return {Array} Breadcrumb items `{ label, url? }`.
|
|
13
13
|
*/
|
|
14
|
-
export const activeBreadcrumbs = ( sections = [], pathname ) =>
|
|
14
|
+
export const activeBreadcrumbs = ( sections = [], pathname ) => activeSection( sections, pathname )?.breadcrumbs || [];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Select the section matching the current route, falling back to the first.
|
|
18
|
+
*
|
|
19
|
+
* @param {Array} sections Wizard sections.
|
|
20
|
+
* @param {string} pathname Current router pathname.
|
|
21
|
+
* @return {Object|undefined} The active section, if any.
|
|
22
|
+
*/
|
|
23
|
+
export const activeSection = ( sections = [], pathname ) => {
|
|
15
24
|
if ( ! sections?.length ) {
|
|
16
|
-
return
|
|
25
|
+
return undefined;
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
return match.breadcrumbs || [];
|
|
27
|
+
return sections.find( section => matchesRoute( section, pathname ) ) || sections[ 0 ];
|
|
20
28
|
};
|
|
21
29
|
|
|
22
30
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal dependencies.
|
|
3
3
|
*/
|
|
4
|
-
import { activeBreadcrumbs, appendSectionName } from './breadcrumbs-select';
|
|
4
|
+
import { activeBreadcrumbs, activeSection, appendSectionName } from './breadcrumbs-select';
|
|
5
5
|
|
|
6
6
|
const SECTIONS = [
|
|
7
7
|
{ path: '/', breadcrumbs: [ { label: 'Audience Management' }, { label: 'Configuration' } ] },
|
|
@@ -122,3 +122,17 @@ describe( 'appendSectionName', () => {
|
|
|
122
122
|
expect( trail ).toEqual( [ { label: 'Access Control' } ] );
|
|
123
123
|
} );
|
|
124
124
|
} );
|
|
125
|
+
|
|
126
|
+
describe( 'activeSection', () => {
|
|
127
|
+
const sections = [ { path: '/settings', exact: true, subHeaderText: 'Intro' }, { path: '/settings/:integrationId' } ];
|
|
128
|
+
|
|
129
|
+
it( 'returns the section matching the route', () => {
|
|
130
|
+
expect( activeSection( sections, '/settings/esp' ) ).toBe( sections[ 1 ] );
|
|
131
|
+
expect( activeSection( sections, '/settings' ).subHeaderText ).toBe( 'Intro' );
|
|
132
|
+
} );
|
|
133
|
+
|
|
134
|
+
it( 'falls back to the first section, or nothing without sections', () => {
|
|
135
|
+
expect( activeSection( sections, '/unknown' ) ).toBe( sections[ 0 ] );
|
|
136
|
+
expect( activeSection( [], '/settings' ) ).toBeUndefined();
|
|
137
|
+
} );
|
|
138
|
+
} );
|
package/src/wizard/index.js
CHANGED
|
@@ -6,27 +6,26 @@ import classnames from 'classnames';
|
|
|
6
6
|
/**
|
|
7
7
|
* WordPress dependencies.
|
|
8
8
|
*/
|
|
9
|
-
// Notice is aliased: `Notice` below is Newspack's own, which this file also uses.
|
|
10
9
|
import {
|
|
11
10
|
DropdownMenu,
|
|
12
11
|
MenuGroup,
|
|
13
12
|
MenuItem,
|
|
14
|
-
Notice
|
|
13
|
+
Notice,
|
|
15
14
|
SlotFillProvider,
|
|
16
15
|
createSlotFill,
|
|
17
16
|
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
|
|
18
17
|
__experimentalVStack as VStack,
|
|
19
18
|
} from '@wordpress/components';
|
|
20
19
|
import { useDispatch, useSelect } from '@wordpress/data';
|
|
21
|
-
import { cloneElement, createInterpolateElement, isValidElement,
|
|
20
|
+
import { cloneElement, createInterpolateElement, isValidElement, useLayoutEffect, useRef, useState, forwardRef } from '@wordpress/element';
|
|
22
21
|
import { __ } from '@wordpress/i18n';
|
|
23
22
|
import { category, chevronLeft, moreVertical } from '@wordpress/icons';
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
25
|
* Internal dependencies
|
|
27
26
|
*/
|
|
28
|
-
import { Footer,
|
|
29
|
-
import { activeBreadcrumbs, appendSectionName } from './breadcrumbs-select';
|
|
27
|
+
import { Footer, DebugBadge, Button, TabbedNavigation, PluginInstaller, SectionHeader, HandoffMessage, Page, Waiting } from '../';
|
|
28
|
+
import { activeBreadcrumbs, activeSection, appendSectionName } from './breadcrumbs-select';
|
|
30
29
|
import Router from '../proxied-imports/router';
|
|
31
30
|
import registerStore, { WIZARD_STORE_NAMESPACE } from './store';
|
|
32
31
|
import WizardSnackbar from './components/WizardSnackbar';
|
|
@@ -55,7 +54,7 @@ const resolveIcon = icon => {
|
|
|
55
54
|
return icon;
|
|
56
55
|
};
|
|
57
56
|
|
|
58
|
-
const { HashRouter, Redirect, Route, Switch, useLocation } = Router;
|
|
57
|
+
const { HashRouter, Redirect, Route, Switch, matchPath, useLocation } = Router;
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
60
|
* Interpolate a translated message's named tags, falling back to plain text.
|
|
@@ -92,7 +91,9 @@ const ResetHeaderData = () => {
|
|
|
92
91
|
const location = useLocation();
|
|
93
92
|
const { resetHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
// Must stay before paint: a passive effect here would run after a section that
|
|
95
|
+
// publishes from a layout effect, wiping the header it just set.
|
|
96
|
+
useLayoutEffect( () => {
|
|
96
97
|
resetHeaderData();
|
|
97
98
|
window.scrollTo( 0, 0 );
|
|
98
99
|
}, [ location.pathname, resetHeaderData ] );
|
|
@@ -104,9 +105,35 @@ const ResetHeaderData = () => {
|
|
|
104
105
|
* Wizard header + content region. Rendered inside the wizard's HashRouter so it
|
|
105
106
|
* can read the current route and derive the active-tab breadcrumb.
|
|
106
107
|
*/
|
|
107
|
-
const WizardHeaderRegion = ( {
|
|
108
|
+
const WizardHeaderRegion = ( {
|
|
109
|
+
hideHeader,
|
|
110
|
+
headerText,
|
|
111
|
+
sections,
|
|
112
|
+
sectionName,
|
|
113
|
+
subTitle,
|
|
114
|
+
actions,
|
|
115
|
+
tabbedNavigation: wizardTabbedNavigation,
|
|
116
|
+
children,
|
|
117
|
+
} ) => {
|
|
108
118
|
const { pathname } = useLocation();
|
|
109
119
|
|
|
120
|
+
// A section can carry its own tabs, built from its route params, in place of
|
|
121
|
+
// the wizard's. The first match wins, as it does in the wizard's `<Switch>`.
|
|
122
|
+
let tabbedNavigation = wizardTabbedNavigation;
|
|
123
|
+
for ( const section of sections ) {
|
|
124
|
+
const match = matchPath( pathname, { path: section.path, exact: section.exact ?? false } );
|
|
125
|
+
if ( match ) {
|
|
126
|
+
if ( typeof section.tabbedNavigation === 'function' ) {
|
|
127
|
+
tabbedNavigation = (
|
|
128
|
+
<TabbedNavigation items={ section.tabbedNavigation( match.params ) }>
|
|
129
|
+
<WizardError />
|
|
130
|
+
</TabbedNavigation>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
110
137
|
if ( hideHeader ) {
|
|
111
138
|
// Without the Page shell the tabs still own the content: it renders
|
|
112
139
|
// inside the active tab's panel.
|
|
@@ -124,8 +151,10 @@ const WizardHeaderRegion = ( { hideHeader, headerText, sections, sectionName, su
|
|
|
124
151
|
// headerData.sectionName (deduped against the current trailing label).
|
|
125
152
|
breadcrumbItems = appendSectionName( breadcrumbItems, sectionName );
|
|
126
153
|
|
|
154
|
+
const sectionSubTitle = activeSection( sections, pathname )?.subHeaderText;
|
|
155
|
+
|
|
127
156
|
return (
|
|
128
|
-
<Page breadcrumbItems={ breadcrumbItems } subTitle={ subTitle } actions={ actions } tabbedNavigation={ tabbedNavigation }>
|
|
157
|
+
<Page breadcrumbItems={ breadcrumbItems } subTitle={ sectionSubTitle ?? subTitle } actions={ actions } tabbedNavigation={ tabbedNavigation }>
|
|
129
158
|
{ children }
|
|
130
159
|
</Page>
|
|
131
160
|
);
|
|
@@ -137,7 +166,8 @@ const WizardHeaderRegion = ( { hideHeader, headerText, sections, sectionName, su
|
|
|
137
166
|
* @property {string} [subHeaderText] The sub-header text, optional.
|
|
138
167
|
* @property {string} [apiSlug] The API slug, optional.
|
|
139
168
|
* @property {string} [className] CSS classes, optional.
|
|
140
|
-
* @property {any[]} sections Array of sections.
|
|
169
|
+
* @property {any[]} sections Array of sections. A section's own `subHeaderText` replaces the wizard's while it is active.
|
|
170
|
+
* Its optional `tabbedNavigation( params )` returns the tab items shown while its route matches.
|
|
141
171
|
* @property {boolean} [hasSimpleFooter] Indicates if a simple footer is used, optional.
|
|
142
172
|
* @property {() => void} [renderAboveSections] Function to render content above sections, optional.
|
|
143
173
|
* @property {string[]} [requiredPlugins] Array of required plugin strings, optional.
|
|
@@ -177,6 +207,7 @@ const Wizard = (
|
|
|
177
207
|
actions,
|
|
178
208
|
backNav,
|
|
179
209
|
badges,
|
|
210
|
+
fullWidth: headerFullWidth,
|
|
180
211
|
sectionDescription,
|
|
181
212
|
sectionMenu,
|
|
182
213
|
sectionName,
|
|
@@ -269,7 +300,7 @@ const Wizard = (
|
|
|
269
300
|
// as page chrome rather than as content.
|
|
270
301
|
const inertGating = window.newspack_aux_data?.inert_gating;
|
|
271
302
|
const inertGatingNotice = inertGating?.show && (
|
|
272
|
-
<
|
|
303
|
+
<Notice status="warning" isDismissible={ false } className="newspack-wizard__inert-gating-notice">
|
|
273
304
|
{ /* The conversion map takes childless elements and fills them from the
|
|
274
305
|
translated string, so jsx-a11y can't see the content they end up with. */ }
|
|
275
306
|
{ interpolateOrPlainText( inertGating.message, {
|
|
@@ -279,7 +310,7 @@ const Wizard = (
|
|
|
279
310
|
/* eslint-enable jsx-a11y/anchor-has-content */
|
|
280
311
|
strong: <strong />,
|
|
281
312
|
} ) }
|
|
282
|
-
</
|
|
313
|
+
</Notice>
|
|
283
314
|
);
|
|
284
315
|
|
|
285
316
|
const content = (
|
|
@@ -303,7 +334,7 @@ const Wizard = (
|
|
|
303
334
|
render={ routerProps => (
|
|
304
335
|
<div
|
|
305
336
|
className={ classnames( 'newspack-wizard__content', className, {
|
|
306
|
-
'newspack-wizard__content--full-width': section.fullWidth,
|
|
337
|
+
'newspack-wizard__content--full-width': headerFullWidth ?? section.fullWidth,
|
|
307
338
|
} ) }
|
|
308
339
|
>
|
|
309
340
|
{ 'function' === typeof renderAboveSections ? renderAboveSections() : null }
|
|
@@ -403,7 +434,7 @@ const Wizard = (
|
|
|
403
434
|
} ) }
|
|
404
435
|
>
|
|
405
436
|
<HashRouter hashType="slash">
|
|
406
|
-
|
|
437
|
+
<DebugBadge />
|
|
407
438
|
<WizardHeaderRegion
|
|
408
439
|
hideHeader={ hideHeader }
|
|
409
440
|
headerText={ headerText }
|
package/src/wizard/index.test.js
CHANGED
|
@@ -7,19 +7,22 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
|
7
7
|
* WordPress dependencies.
|
|
8
8
|
*/
|
|
9
9
|
import apiFetch from '@wordpress/api-fetch';
|
|
10
|
+
import { select, useDispatch } from '@wordpress/data';
|
|
11
|
+
import { useLayoutEffect, useState } from '@wordpress/element';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Internal dependencies.
|
|
13
15
|
*/
|
|
14
16
|
import Wizard from './';
|
|
15
17
|
import { useWizardData } from './store/utils';
|
|
18
|
+
import { WIZARD_STORE_NAMESPACE } from './store';
|
|
16
19
|
|
|
17
20
|
jest.mock( '@wordpress/api-fetch', () => jest.fn() );
|
|
18
21
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
window.newspack_aux_data = { is_debug_mode: false };
|
|
22
|
+
// Localized onto every real wizard screen; the footer's ExternalLink needs a
|
|
23
|
+
// string href.
|
|
22
24
|
window.newspack_urls = { support: 'https://help.newspack.com/' };
|
|
25
|
+
window.scrollTo = jest.fn();
|
|
23
26
|
|
|
24
27
|
const SETTINGS = { minimumDonation: '5' };
|
|
25
28
|
|
|
@@ -154,3 +157,166 @@ describe( 'Wizard', () => {
|
|
|
154
157
|
expect( counts.wizard ).toBe( 0 );
|
|
155
158
|
} );
|
|
156
159
|
} );
|
|
160
|
+
|
|
161
|
+
const NarrowingSection = () => {
|
|
162
|
+
const { setHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
|
|
163
|
+
useLayoutEffect( () => {
|
|
164
|
+
setHeaderData( { fullWidth: false } );
|
|
165
|
+
}, [ setHeaderData ] );
|
|
166
|
+
return <div>Narrowing section</div>;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const RecoveringSection = () => {
|
|
170
|
+
const { setHeaderData } = useDispatch( WIZARD_STORE_NAMESPACE );
|
|
171
|
+
const [ failed, setFailed ] = useState( true );
|
|
172
|
+
useLayoutEffect( () => {
|
|
173
|
+
setHeaderData( { fullWidth: failed ? false : undefined } );
|
|
174
|
+
}, [ setHeaderData, failed ] );
|
|
175
|
+
return <button onClick={ () => setFailed( false ) }>Retry</button>;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Two sections, because that is what mounts ResetHeaderData: a single-section
|
|
179
|
+
// wizard never exercises the reset these overrides have to survive.
|
|
180
|
+
const widthSections = renderSection => [
|
|
181
|
+
{ label: 'List', path: '/', exact: true, fullWidth: true, render: renderSection },
|
|
182
|
+
{ label: 'Other', path: '/other', render: () => <div>Other section</div> },
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
describe( 'Wizard content width', () => {
|
|
186
|
+
beforeEach( () => {
|
|
187
|
+
apiFetch.mockReset();
|
|
188
|
+
} );
|
|
189
|
+
|
|
190
|
+
it( 'renders full-width when the section declares it and nothing overrides it', async () => {
|
|
191
|
+
const { container } = render(
|
|
192
|
+
<Wizard
|
|
193
|
+
headerText="Test wizard"
|
|
194
|
+
sections={ widthSections( () => (
|
|
195
|
+
<div>List content</div>
|
|
196
|
+
) ) }
|
|
197
|
+
/>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect( await screen.findByText( 'List content' ) ).toBeInTheDocument();
|
|
201
|
+
expect( container.querySelector( '.newspack-wizard__content' ) ).toHaveClass( 'newspack-wizard__content--full-width' );
|
|
202
|
+
} );
|
|
203
|
+
|
|
204
|
+
it( 'falls back to the standard width when a section overrides fullWidth before paint', async () => {
|
|
205
|
+
const { container } = render(
|
|
206
|
+
<Wizard
|
|
207
|
+
headerText="Test wizard"
|
|
208
|
+
sections={ widthSections( () => (
|
|
209
|
+
<NarrowingSection />
|
|
210
|
+
) ) }
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect( await screen.findByText( 'Narrowing section' ) ).toBeInTheDocument();
|
|
215
|
+
expect( container.querySelector( '.newspack-wizard__content' ) ).not.toHaveClass( 'newspack-wizard__content--full-width' );
|
|
216
|
+
} );
|
|
217
|
+
|
|
218
|
+
it( 'restores the declared width when the section clears its override', async () => {
|
|
219
|
+
const { container } = render(
|
|
220
|
+
<Wizard
|
|
221
|
+
headerText="Test wizard"
|
|
222
|
+
sections={ widthSections( () => (
|
|
223
|
+
<RecoveringSection />
|
|
224
|
+
) ) }
|
|
225
|
+
/>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const retry = await screen.findByRole( 'button', { name: 'Retry' } );
|
|
229
|
+
expect( container.querySelector( '.newspack-wizard__content' ) ).not.toHaveClass( 'newspack-wizard__content--full-width' );
|
|
230
|
+
|
|
231
|
+
fireEvent.click( retry );
|
|
232
|
+
|
|
233
|
+
await waitFor( () => expect( container.querySelector( '.newspack-wizard__content' ) ).toHaveClass( 'newspack-wizard__content--full-width' ) );
|
|
234
|
+
} );
|
|
235
|
+
|
|
236
|
+
it( 'keeps a section header published from a layout effect through the mount reset', async () => {
|
|
237
|
+
render(
|
|
238
|
+
<Wizard
|
|
239
|
+
headerText="Test wizard"
|
|
240
|
+
sections={ widthSections( () => (
|
|
241
|
+
<NarrowingSection />
|
|
242
|
+
) ) }
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect( await screen.findByText( 'Narrowing section' ) ).toBeInTheDocument();
|
|
247
|
+
expect( select( WIZARD_STORE_NAMESPACE ).getHeaderData().fullWidth ).toBe( false );
|
|
248
|
+
} );
|
|
249
|
+
} );
|
|
250
|
+
|
|
251
|
+
describe( 'Wizard section tabs', () => {
|
|
252
|
+
const sections = [
|
|
253
|
+
{ label: 'List', path: '/', exact: true, render: () => <div>List content</div> },
|
|
254
|
+
{
|
|
255
|
+
path: '/items/:itemId/logs/:tab?',
|
|
256
|
+
isHidden: true,
|
|
257
|
+
render: () => <div>Logs content</div>,
|
|
258
|
+
tabbedNavigation: ( { itemId } ) => [
|
|
259
|
+
{ label: 'First Tab', path: `/items/${ itemId }/logs`, exact: true },
|
|
260
|
+
{ label: 'Second Tab', path: `/items/${ itemId }/logs/second`, exact: true },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
beforeEach( () => {
|
|
266
|
+
apiFetch.mockReset();
|
|
267
|
+
} );
|
|
268
|
+
|
|
269
|
+
afterEach( () => {
|
|
270
|
+
window.location.hash = '';
|
|
271
|
+
} );
|
|
272
|
+
|
|
273
|
+
it( 'shows the tabs a section builds from its route params while it matches', async () => {
|
|
274
|
+
window.location.hash = '#/items/42/logs/second';
|
|
275
|
+
render( <Wizard headerText="Test wizard" sections={ sections } /> );
|
|
276
|
+
|
|
277
|
+
expect( await screen.findByText( 'Logs content' ) ).toBeInTheDocument();
|
|
278
|
+
expect( screen.getByRole( 'tab', { name: 'First Tab' } ) ).toHaveAttribute( 'href', '#/items/42/logs' );
|
|
279
|
+
expect( screen.getByRole( 'tab', { name: 'Second Tab' } ) ).toHaveAttribute( 'aria-selected', 'true' );
|
|
280
|
+
} );
|
|
281
|
+
|
|
282
|
+
it( 'leaves the tabs out on a route the section does not match', async () => {
|
|
283
|
+
window.location.hash = '#/';
|
|
284
|
+
render( <Wizard headerText="Test wizard" sections={ sections } /> );
|
|
285
|
+
|
|
286
|
+
expect( await screen.findByText( 'List content' ) ).toBeInTheDocument();
|
|
287
|
+
expect( screen.queryByRole( 'tab' ) ).toBeNull();
|
|
288
|
+
} );
|
|
289
|
+
} );
|
|
290
|
+
|
|
291
|
+
describe( 'Wizard section subtitle', () => {
|
|
292
|
+
beforeEach( () => {
|
|
293
|
+
apiFetch.mockReset();
|
|
294
|
+
apiFetch.mockResolvedValue( {} );
|
|
295
|
+
} );
|
|
296
|
+
|
|
297
|
+
it( "shows the active section's subHeaderText in the header, falling back to the wizard's", async () => {
|
|
298
|
+
window.location.hash = '#/list';
|
|
299
|
+
const { container } = render(
|
|
300
|
+
<Wizard
|
|
301
|
+
headerText="Test wizard"
|
|
302
|
+
subHeaderText="Wizard intro"
|
|
303
|
+
sections={ [
|
|
304
|
+
{ label: 'List', path: '/list', exact: true, subHeaderText: 'List intro', render: () => <div>List view</div> },
|
|
305
|
+
{ label: 'Item', path: '/list/:id', isHidden: true, render: () => <div>Item view</div> },
|
|
306
|
+
] }
|
|
307
|
+
/>
|
|
308
|
+
);
|
|
309
|
+
const subtitle = () => container.querySelector( '.newspack-page__header-subtitle' )?.textContent;
|
|
310
|
+
|
|
311
|
+
expect( await screen.findByText( 'List view' ) ).toBeInTheDocument();
|
|
312
|
+
expect( subtitle() ).toBe( 'List intro' );
|
|
313
|
+
|
|
314
|
+
act( () => {
|
|
315
|
+
window.location.hash = '#/list/esp';
|
|
316
|
+
window.dispatchEvent( new HashChangeEvent( 'hashchange' ) );
|
|
317
|
+
} );
|
|
318
|
+
|
|
319
|
+
expect( await screen.findByText( 'Item view' ) ).toBeInTheDocument();
|
|
320
|
+
expect( subtitle() ).toBe( 'Wizard intro' );
|
|
321
|
+
} );
|
|
322
|
+
} );
|
|
@@ -16,6 +16,8 @@ import { createAction } from './utils.js';
|
|
|
16
16
|
export const WIZARD_STORE_NAMESPACE = 'newspack/wizards';
|
|
17
17
|
|
|
18
18
|
const DEFAULT_STATE = {
|
|
19
|
+
// `fullWidth` is deliberately absent: a default here would narrow every
|
|
20
|
+
// full-width section on reset, since Wizard only falls back when it is unset.
|
|
19
21
|
headerData: {
|
|
20
22
|
actions: [],
|
|
21
23
|
backNav: '',
|