@automattic/jetpack-shared-extension-utils 0.9.1 → 0.10.0
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 +11 -1
- package/index.js +1 -0
- package/jest-globals.js +12 -0
- package/jest.config.js +13 -0
- package/package.json +17 -3
- package/src/hooks/use-module-status/index.js +114 -0
- package/src/hooks/use-module-status/test/index.test.js +153 -0
- package/src/plan-utils.js +44 -39
- package/src/with-has-warning-is-interactive-class-names/index.jsx +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.
|
|
8
|
+
## [0.10.0] - 2023-03-27
|
|
9
|
+
### Added
|
|
10
|
+
- useModuleStatus: Add new hook to enable or disable Jetpack modules. [#29044]
|
|
11
|
+
|
|
12
|
+
## [0.9.2] - 2023-03-23
|
|
13
|
+
### Changed
|
|
14
|
+
- Updated package dependencies.
|
|
15
|
+
|
|
16
|
+
## [0.9.1] - 2023-03-08
|
|
9
17
|
### Changed
|
|
10
18
|
- Updated package dependencies. [#29216]
|
|
11
19
|
|
|
@@ -177,6 +185,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
177
185
|
### Changed
|
|
178
186
|
- Core: prepare utility for release
|
|
179
187
|
|
|
188
|
+
[0.10.0]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.9.2...0.10.0
|
|
189
|
+
[0.9.2]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.9.1...0.9.2
|
|
180
190
|
[0.9.1]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.9.0...0.9.1
|
|
181
191
|
[0.9.0]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.8.4...0.9.0
|
|
182
192
|
[0.8.4]: https://github.com/Automattic/jetpack-shared-extension-utils/compare/0.8.3...0.8.4
|
package/index.js
CHANGED
|
@@ -15,3 +15,4 @@ export {
|
|
|
15
15
|
} from './src/plan-utils';
|
|
16
16
|
export { default as isCurrentUserConnected } from './src/is-current-user-connected';
|
|
17
17
|
export { default as useAnalytics } from './src/hooks/use-analytics';
|
|
18
|
+
export { default as useModuleStatus } from './src/hooks/use-module-status';
|
package/jest-globals.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
if ( ! window.matchMedia ) {
|
|
2
|
+
window.matchMedia = query => ( {
|
|
3
|
+
matches: false,
|
|
4
|
+
media: query,
|
|
5
|
+
onchange: null,
|
|
6
|
+
addListener: jest.fn(), // deprecated
|
|
7
|
+
removeListener: jest.fn(), // deprecated
|
|
8
|
+
addEventListener: jest.fn(),
|
|
9
|
+
removeEventListener: jest.fn(),
|
|
10
|
+
dispatchEvent: jest.fn(),
|
|
11
|
+
} );
|
|
12
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const baseConfig = require( 'jetpack-js-tools/jest/config.base.js' );
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
...baseConfig,
|
|
5
|
+
roots: [ '<rootDir>/src' ],
|
|
6
|
+
setupFiles: [ ...baseConfig.setupFiles, '<rootDir>/jest-globals.js' ],
|
|
7
|
+
transform: {
|
|
8
|
+
...baseConfig.transform,
|
|
9
|
+
'\\.[jt]sx?$': require( 'jetpack-js-tools/jest/babel-jest-config-factory.js' )(
|
|
10
|
+
require.resolve
|
|
11
|
+
),
|
|
12
|
+
},
|
|
13
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/jetpack-shared-extension-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Utility functions used by the block editor extensions",
|
|
5
5
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/shared-extension-utils/#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "GPL-2.0-or-later",
|
|
15
15
|
"author": "Automattic",
|
|
16
|
-
"scripts": {
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "jest"
|
|
18
|
+
},
|
|
17
19
|
"dependencies": {
|
|
18
20
|
"@automattic/jetpack-analytics": "workspace:*",
|
|
19
21
|
"@automattic/jetpack-connection": "workspace:*",
|
|
22
|
+
"@wordpress/api-fetch": "6.23.0",
|
|
20
23
|
"@wordpress/compose": "6.5.0",
|
|
21
24
|
"@wordpress/element": "5.5.0",
|
|
22
25
|
"@wordpress/i18n": "4.28.0",
|
|
@@ -27,7 +30,18 @@
|
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"@babel/core": "7.20.12",
|
|
29
32
|
"@babel/preset-react": "7.18.6",
|
|
30
|
-
"
|
|
33
|
+
"babel-jest": "29.3.1",
|
|
34
|
+
"jest": "29.3.1",
|
|
35
|
+
"jest-environment-jsdom": "29.3.1",
|
|
36
|
+
"react": "18.2.0",
|
|
37
|
+
"react-dom": "18.2.0",
|
|
38
|
+
"jetpack-js-tools": "workspace:*",
|
|
39
|
+
"@automattic/jetpack-webpack-config": "workspace:*",
|
|
40
|
+
"@wordpress/babel-plugin-import-jsx-pragma": "4.9.0",
|
|
41
|
+
"@testing-library/dom": "8.19.1",
|
|
42
|
+
"@testing-library/react": "13.4.0",
|
|
43
|
+
"@testing-library/user-event": "14.4.3",
|
|
44
|
+
"@babel/plugin-transform-react-jsx": "7.20.13"
|
|
31
45
|
},
|
|
32
46
|
"exports": {
|
|
33
47
|
".": "./index.js"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import apiFetch from '@wordpress/api-fetch';
|
|
2
|
+
import { useEffect, useState, useMemo, useCallback } from '@wordpress/element';
|
|
3
|
+
import { isSimpleSite } from '../../site-type-utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetch information about all Jetpack modules.
|
|
7
|
+
*
|
|
8
|
+
* @returns {Promise<object>} Details about all available modules on the site.
|
|
9
|
+
*/
|
|
10
|
+
async function fetchModules() {
|
|
11
|
+
try {
|
|
12
|
+
const result = await apiFetch( {
|
|
13
|
+
path: `/jetpack/v4/module/all`,
|
|
14
|
+
method: 'GET',
|
|
15
|
+
} );
|
|
16
|
+
return result;
|
|
17
|
+
} catch ( error ) {
|
|
18
|
+
return error.message;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Update a Jetpack module's status.
|
|
24
|
+
*
|
|
25
|
+
* @param {*} name - The module's name.
|
|
26
|
+
* @param {*} toggle - New module status.
|
|
27
|
+
* @returns {Promise<boolean>} Promise that resolves to the new module status.
|
|
28
|
+
*/
|
|
29
|
+
async function changeModuleStatus( name, toggle ) {
|
|
30
|
+
const result = await apiFetch( {
|
|
31
|
+
path: `/jetpack/v4/module/${ name }/active`,
|
|
32
|
+
method: 'POST',
|
|
33
|
+
data: {
|
|
34
|
+
active: toggle,
|
|
35
|
+
},
|
|
36
|
+
} );
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Determine whethher a Jetpack module is active.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} name - The module's name
|
|
44
|
+
* @returns {Promise<boolean>} Whether the module is active.
|
|
45
|
+
*/
|
|
46
|
+
async function isJetpackModuleActive( name ) {
|
|
47
|
+
// On WordPress.com Simple sites, all modules are always active.
|
|
48
|
+
if ( isSimpleSite() ) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fetch module info.
|
|
53
|
+
try {
|
|
54
|
+
// Check if module is active.
|
|
55
|
+
const modulesInfo = await fetchModules();
|
|
56
|
+
if ( ! modulesInfo || ! modulesInfo.hasOwnProperty( name ) ) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return !! modulesInfo[ name ].activated;
|
|
60
|
+
} catch ( e ) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Manage a Jetpack module's status (get and set).
|
|
67
|
+
*
|
|
68
|
+
* @param {string} name - The module's name.
|
|
69
|
+
* @returns {boolean} Whether the module is active.
|
|
70
|
+
*/
|
|
71
|
+
const useModuleStatus = name => {
|
|
72
|
+
const [ isLoadingModules, setIsLoadingModules ] = useState( Boolean( name ) );
|
|
73
|
+
const [ isChangingStatus, setIsChangingStatus ] = useState( false );
|
|
74
|
+
const [ isModuleActive, setModuleStatus ] = useState( false );
|
|
75
|
+
|
|
76
|
+
// Get module status.
|
|
77
|
+
useEffect( () => {
|
|
78
|
+
if ( ! name ) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setIsLoadingModules( true );
|
|
83
|
+
|
|
84
|
+
isJetpackModuleActive( name ).then( moduleStatus => {
|
|
85
|
+
setModuleStatus( moduleStatus );
|
|
86
|
+
setIsLoadingModules( false );
|
|
87
|
+
} );
|
|
88
|
+
}, [ name ] );
|
|
89
|
+
|
|
90
|
+
const changeStatus = useCallback(
|
|
91
|
+
newModuleStatus => {
|
|
92
|
+
if ( ! name || isModuleActive === newModuleStatus ) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
setIsChangingStatus( true );
|
|
96
|
+
changeModuleStatus( name, newModuleStatus )
|
|
97
|
+
.then( () => {
|
|
98
|
+
setModuleStatus( newModuleStatus );
|
|
99
|
+
setIsChangingStatus( false );
|
|
100
|
+
} )
|
|
101
|
+
.catch( () => {
|
|
102
|
+
setIsChangingStatus( false );
|
|
103
|
+
} );
|
|
104
|
+
},
|
|
105
|
+
[ name, isModuleActive ]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return useMemo(
|
|
109
|
+
() => ( { isLoadingModules, isChangingStatus, isModuleActive, changeStatus } ),
|
|
110
|
+
[ isLoadingModules, isChangingStatus, isModuleActive, changeStatus ]
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default useModuleStatus;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
2
|
+
import { isSimpleSite } from '../../../site-type-utils';
|
|
3
|
+
import useModuleStatus from '../index';
|
|
4
|
+
|
|
5
|
+
jest.mock( '../../../site-type-utils' );
|
|
6
|
+
|
|
7
|
+
describe( 'useModuleStatus hook', () => {
|
|
8
|
+
const originalFetch = window.fetch;
|
|
9
|
+
|
|
10
|
+
beforeEach( () => {
|
|
11
|
+
isSimpleSite.mockReset();
|
|
12
|
+
// eslint-disable-next-line jest/prefer-spy-on -- Nothing to spy on.
|
|
13
|
+
window.fetch = jest.fn();
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
afterEach( () => {
|
|
17
|
+
window.fetch = originalFetch;
|
|
18
|
+
} );
|
|
19
|
+
test( 'should not try to fetch modules if no name is provided', () => {
|
|
20
|
+
const { result } = renderHook( name => useModuleStatus( name ), {
|
|
21
|
+
initialProps: '',
|
|
22
|
+
} );
|
|
23
|
+
const { changeStatus, ...otherProps } = result.current;
|
|
24
|
+
|
|
25
|
+
expect( otherProps ).toStrictEqual( {
|
|
26
|
+
isLoadingModules: false,
|
|
27
|
+
isChangingStatus: false,
|
|
28
|
+
isModuleActive: false,
|
|
29
|
+
} );
|
|
30
|
+
} );
|
|
31
|
+
|
|
32
|
+
test( 'jetpack module is active on not simple sites.', async () => {
|
|
33
|
+
isSimpleSite.mockReturnValueOnce( false );
|
|
34
|
+
|
|
35
|
+
window.fetch.mockReturnValueOnce(
|
|
36
|
+
Promise.resolve( {
|
|
37
|
+
status: 200,
|
|
38
|
+
json: () =>
|
|
39
|
+
Promise.resolve( {
|
|
40
|
+
subscriptions: {
|
|
41
|
+
activated: true,
|
|
42
|
+
},
|
|
43
|
+
} ),
|
|
44
|
+
} )
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const { result } = renderHook( name => useModuleStatus( name ), {
|
|
48
|
+
initialProps: 'subscriptions',
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
expect( result.current.isModuleActive ).toBe( false );
|
|
52
|
+
await waitFor( async () => expect( result.current.isModuleActive ).toBe( true ) );
|
|
53
|
+
|
|
54
|
+
expect( window.fetch ).toHaveBeenCalledWith(
|
|
55
|
+
'/jetpack/v4/module/all?_locale=user',
|
|
56
|
+
expect.anything()
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const { changeStatus, ...otherProps } = result.current;
|
|
60
|
+
|
|
61
|
+
expect( otherProps ).toStrictEqual( {
|
|
62
|
+
isLoadingModules: false,
|
|
63
|
+
isChangingStatus: false,
|
|
64
|
+
isModuleActive: true,
|
|
65
|
+
} );
|
|
66
|
+
} );
|
|
67
|
+
|
|
68
|
+
test( 'jetpack module is active on simple sites.', async () => {
|
|
69
|
+
isSimpleSite.mockReturnValueOnce( true );
|
|
70
|
+
|
|
71
|
+
window.fetch.mockReturnValueOnce(
|
|
72
|
+
Promise.resolve( {
|
|
73
|
+
status: 200,
|
|
74
|
+
json: () =>
|
|
75
|
+
Promise.resolve( {
|
|
76
|
+
subscriptions: {
|
|
77
|
+
activated: false,
|
|
78
|
+
},
|
|
79
|
+
} ),
|
|
80
|
+
} )
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const { result } = renderHook( name => useModuleStatus( name ), {
|
|
84
|
+
initialProps: 'subscriptions',
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
expect( result.current.isModuleActive ).toBe( false );
|
|
88
|
+
await waitFor( async () => expect( result.current.isModuleActive ).toBe( true ) );
|
|
89
|
+
expect( window.fetch ).not.toHaveBeenCalled();
|
|
90
|
+
|
|
91
|
+
const { changeStatus, ...otherProps } = result.current;
|
|
92
|
+
|
|
93
|
+
expect( otherProps ).toStrictEqual( {
|
|
94
|
+
isLoadingModules: false,
|
|
95
|
+
isChangingStatus: false,
|
|
96
|
+
isModuleActive: true,
|
|
97
|
+
} );
|
|
98
|
+
} );
|
|
99
|
+
|
|
100
|
+
test( 'change jetpack module status', async () => {
|
|
101
|
+
isSimpleSite.mockReturnValueOnce( false );
|
|
102
|
+
|
|
103
|
+
window.fetch.mockReturnValueOnce(
|
|
104
|
+
Promise.resolve( {
|
|
105
|
+
status: 200,
|
|
106
|
+
json: () =>
|
|
107
|
+
Promise.resolve( {
|
|
108
|
+
subscriptions: {
|
|
109
|
+
activated: true,
|
|
110
|
+
},
|
|
111
|
+
} ),
|
|
112
|
+
} )
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const { result } = renderHook( name => useModuleStatus( name ), {
|
|
116
|
+
initialProps: 'subscriptions',
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
await waitFor( async () => expect( result.current.isModuleActive ).toBe( true ) );
|
|
120
|
+
expect( window.fetch ).toHaveBeenCalledWith(
|
|
121
|
+
'/jetpack/v4/module/all?_locale=user',
|
|
122
|
+
expect.anything()
|
|
123
|
+
);
|
|
124
|
+
window.fetch.mockReset();
|
|
125
|
+
|
|
126
|
+
window.fetch.mockReturnValueOnce(
|
|
127
|
+
Promise.resolve( {
|
|
128
|
+
status: 200,
|
|
129
|
+
json: () => Promise.resolve( {} ),
|
|
130
|
+
} )
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
act( () => {
|
|
134
|
+
result.current.changeStatus( false );
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
expect( result.current.isChangingStatus ).toBe( true );
|
|
138
|
+
expect( result.current.isModuleActive ).toBe( true );
|
|
139
|
+
|
|
140
|
+
await waitFor( async () => expect( result.current.isChangingStatus ).toBe( false ) );
|
|
141
|
+
expect( result.current.isModuleActive ).toBe( false );
|
|
142
|
+
|
|
143
|
+
Promise.resolve( {
|
|
144
|
+
status: 200,
|
|
145
|
+
json: () => Promise.resolve( {} ),
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
expect( window.fetch ).toHaveBeenCalledWith(
|
|
149
|
+
'/jetpack/v4/module/subscriptions/active?_locale=user',
|
|
150
|
+
expect.anything()
|
|
151
|
+
);
|
|
152
|
+
} );
|
|
153
|
+
} );
|
package/src/plan-utils.js
CHANGED
|
@@ -22,45 +22,50 @@ export function getUpgradeUrl( { planSlug, plan, postId, postType } ) {
|
|
|
22
22
|
const planPathSlug = startsWith( planSlug, 'jetpack_' ) ? planSlug : get( plan, [ 'path_slug' ] );
|
|
23
23
|
|
|
24
24
|
// The full site editor has no set post type.
|
|
25
|
-
const redirect_to = (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
25
|
+
const redirect_to = (
|
|
26
|
+
undefined === postType
|
|
27
|
+
? () => {
|
|
28
|
+
const queryParams = new URLSearchParams( window.location.search );
|
|
29
|
+
|
|
30
|
+
return addQueryArgs(
|
|
31
|
+
window.location.protocol +
|
|
32
|
+
`//${ getSiteFragment().replace( '::', '/' ) }/wp-admin/site-editor.php`,
|
|
33
|
+
{
|
|
34
|
+
postId: queryParams.get( 'postId' ),
|
|
35
|
+
postType: queryParams.get( 'postType' ),
|
|
36
|
+
plan_upgraded: 1,
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
: () => {
|
|
41
|
+
// The editor for CPTs has an `edit/` route fragment prefixed.
|
|
42
|
+
const postTypeEditorRoutePrefix = [ 'page', 'post' ].includes( postType ) ? '' : 'edit';
|
|
43
|
+
|
|
44
|
+
// Post-checkout: redirect back here.
|
|
45
|
+
return isSimpleSite()
|
|
46
|
+
? addQueryArgs(
|
|
47
|
+
'/' +
|
|
48
|
+
compact( [
|
|
49
|
+
postTypeEditorRoutePrefix,
|
|
50
|
+
postType,
|
|
51
|
+
getSiteFragment(),
|
|
52
|
+
postId,
|
|
53
|
+
] ).join( '/' ),
|
|
54
|
+
{
|
|
55
|
+
plan_upgraded: 1,
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
: addQueryArgs(
|
|
59
|
+
window.location.protocol +
|
|
60
|
+
`//${ getSiteFragment().replace( '::', '/' ) }/wp-admin/post.php`,
|
|
61
|
+
{
|
|
62
|
+
action: 'edit',
|
|
63
|
+
post: postId,
|
|
64
|
+
plan_upgraded: 1,
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
)();
|
|
64
69
|
|
|
65
70
|
// Redirect to calypso plans page for WoC sites.
|
|
66
71
|
if ( isAtomicSite() ) {
|
|
@@ -10,11 +10,12 @@ import './style.scss';
|
|
|
10
10
|
// We thus add a new `is-interactive` class to be able to override that behavior.
|
|
11
11
|
export default name =>
|
|
12
12
|
createHigherOrderComponent(
|
|
13
|
-
BlockListBlock => props =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
BlockListBlock => props =>
|
|
14
|
+
(
|
|
15
|
+
<BlockListBlock
|
|
16
|
+
{ ...props }
|
|
17
|
+
className={ props.name === name ? 'has-warning is-interactive' : props.className }
|
|
18
|
+
/>
|
|
19
|
+
),
|
|
19
20
|
'withHasWarningIsInteractiveClassNames'
|
|
20
21
|
);
|