@wordpress/e2e-test-utils-playwright 1.47.0 → 1.48.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/package.json +3 -3
- package/src/admin/create-new-post.ts +47 -0
- package/src/admin/edit-post.ts +24 -0
- package/{build/admin/get-page-error.js → src/admin/get-page-error.ts} +12 -9
- package/src/admin/index.ts +52 -0
- package/src/admin/visit-admin-page.ts +43 -0
- package/src/admin/visit-site-editor.ts +62 -0
- package/src/admin/wait-for-site-editor.ts +52 -0
- package/src/config.ts +12 -0
- package/src/editor/click-block-options-menu-item.ts +18 -0
- package/src/editor/click-block-toolbar-button.ts +21 -0
- package/src/editor/get-blocks.ts +75 -0
- package/src/editor/get-edited-post-content.ts +19 -0
- package/src/editor/index.ts +95 -0
- package/src/editor/insert-block.ts +54 -0
- package/src/editor/open-document-settings-sidebar.ts +31 -0
- package/src/editor/preview.ts +32 -0
- package/src/editor/publish-post.ts +51 -0
- package/src/editor/save-draft.ts +20 -0
- package/src/editor/select-blocks.ts +48 -0
- package/src/editor/set-content.ts +24 -0
- package/src/editor/set-is-fixed-toolbar.ts +20 -0
- package/src/editor/set-preferences.ts +37 -0
- package/src/editor/show-block-toolbar.ts +20 -0
- package/src/editor/site-editor.ts +55 -0
- package/src/editor/switch-editor-tool.ts +34 -0
- package/src/editor/switch-to-legacy-canvas.ts +20 -0
- package/src/editor/transform-block-to.ts +34 -0
- package/src/index.ts +8 -0
- package/src/lighthouse/index.ts +75 -0
- package/src/metrics/index.ts +441 -0
- package/src/page-utils/drag-files.ts +173 -0
- package/src/page-utils/emulate-network-conditions.ts +101 -0
- package/src/page-utils/index.ts +54 -0
- package/src/page-utils/is-current-url.ts +20 -0
- package/src/page-utils/keycodes.ts +48 -0
- package/src/page-utils/press-keys.ts +195 -0
- package/{build → src}/page-utils/set-browser-viewport.js +14 -12
- package/src/request-utils/blocks.ts +58 -0
- package/src/request-utils/comments.ts +80 -0
- package/src/request-utils/gutenberg-experiments.ts +62 -0
- package/src/request-utils/index.ts +222 -0
- package/src/request-utils/login.ts +34 -0
- package/src/request-utils/media.ts +102 -0
- package/src/request-utils/menus.ts +145 -0
- package/src/request-utils/pages.ts +75 -0
- package/src/request-utils/patterns.ts +31 -0
- package/src/request-utils/plugins.ts +103 -0
- package/src/request-utils/posts.ts +69 -0
- package/src/request-utils/preferences.ts +63 -0
- package/{build/request-utils/records.js → src/request-utils/records.ts} +15 -10
- package/src/request-utils/rest.ts +222 -0
- package/src/request-utils/site-settings.ts +58 -0
- package/src/request-utils/templates.ts +83 -0
- package/src/request-utils/themes.ts +110 -0
- package/src/request-utils/users.ts +125 -0
- package/src/request-utils/widgets.js +68 -0
- package/src/test.ts +204 -0
- package/src/types.ts +13 -0
- package/build/admin/create-new-post.js +0 -31
- package/build/admin/create-new-post.js.map +0 -1
- package/build/admin/edit-post.js +0 -20
- package/build/admin/edit-post.js.map +0 -1
- package/build/admin/get-page-error.js.map +0 -1
- package/build/admin/index.js +0 -40
- package/build/admin/index.js.map +0 -1
- package/build/admin/visit-admin-page.js +0 -32
- package/build/admin/visit-admin-page.js.map +0 -1
- package/build/admin/visit-site-editor.js +0 -45
- package/build/admin/visit-site-editor.js.map +0 -1
- package/build/admin/wait-for-site-editor.js +0 -41
- package/build/admin/wait-for-site-editor.js.map +0 -1
- package/build/config.js +0 -13
- package/build/config.js.map +0 -1
- package/build/editor/click-block-options-menu-item.js +0 -17
- package/build/editor/click-block-options-menu-item.js.map +0 -1
- package/build/editor/click-block-toolbar-button.js +0 -16
- package/build/editor/click-block-toolbar-button.js.map +0 -1
- package/build/editor/get-blocks.js +0 -45
- package/build/editor/get-blocks.js.map +0 -1
- package/build/editor/get-edited-post-content.js +0 -15
- package/build/editor/get-edited-post-content.js.map +0 -1
- package/build/editor/index.js +0 -75
- package/build/editor/index.js.map +0 -1
- package/build/editor/insert-block.js +0 -23
- package/build/editor/insert-block.js.map +0 -1
- package/build/editor/open-document-settings-sidebar.js +0 -27
- package/build/editor/open-document-settings-sidebar.js.map +0 -1
- package/build/editor/preview.js +0 -21
- package/build/editor/preview.js.map +0 -1
- package/build/editor/publish-post.js +0 -45
- package/build/editor/publish-post.js.map +0 -1
- package/build/editor/save-draft.js +0 -18
- package/build/editor/save-draft.js.map +0 -1
- package/build/editor/select-blocks.js +0 -28
- package/build/editor/select-blocks.js.map +0 -1
- package/build/editor/set-content.js +0 -17
- package/build/editor/set-content.js.map +0 -1
- package/build/editor/set-is-fixed-toolbar.js +0 -18
- package/build/editor/set-is-fixed-toolbar.js.map +0 -1
- package/build/editor/set-preferences.js +0 -21
- package/build/editor/set-preferences.js.map +0 -1
- package/build/editor/show-block-toolbar.js +0 -19
- package/build/editor/show-block-toolbar.js.map +0 -1
- package/build/editor/site-editor.js +0 -44
- package/build/editor/site-editor.js.map +0 -1
- package/build/editor/switch-editor-tool.js +0 -33
- package/build/editor/switch-editor-tool.js.map +0 -1
- package/build/editor/switch-to-legacy-canvas.js +0 -18
- package/build/editor/switch-to-legacy-canvas.js.map +0 -1
- package/build/editor/transform-block-to.js +0 -24
- package/build/editor/transform-block-to.js.map +0 -1
- package/build/index.js +0 -19
- package/build/index.js.map +0 -1
- package/build/lighthouse/index.js +0 -61
- package/build/lighthouse/index.js.map +0 -1
- package/build/metrics/index.js +0 -314
- package/build/metrics/index.js.map +0 -1
- package/build/page-utils/drag-files.js +0 -119
- package/build/page-utils/drag-files.js.map +0 -1
- package/build/page-utils/emulate-network-conditions.js +0 -71
- package/build/page-utils/emulate-network-conditions.js.map +0 -1
- package/build/page-utils/index.js +0 -37
- package/build/page-utils/index.js.map +0 -1
- package/build/page-utils/is-current-url.js +0 -21
- package/build/page-utils/is-current-url.js.map +0 -1
- package/build/page-utils/keycodes.js +0 -41
- package/build/page-utils/keycodes.js.map +0 -1
- package/build/page-utils/press-keys.js +0 -141
- package/build/page-utils/press-keys.js.map +0 -1
- package/build/page-utils/set-browser-viewport.js.map +0 -1
- package/build/request-utils/blocks.js +0 -40
- package/build/request-utils/blocks.js.map +0 -1
- package/build/request-utils/comments.js +0 -52
- package/build/request-utils/comments.js.map +0 -1
- package/build/request-utils/gutenberg-experiments.js +0 -47
- package/build/request-utils/gutenberg-experiments.js.map +0 -1
- package/build/request-utils/index.js +0 -196
- package/build/request-utils/index.js.map +0 -1
- package/build/request-utils/login.js +0 -21
- package/build/request-utils/login.js.map +0 -1
- package/build/request-utils/media.js +0 -106
- package/build/request-utils/media.js.map +0 -1
- package/build/request-utils/menus.js +0 -116
- package/build/request-utils/menus.js.map +0 -1
- package/build/request-utils/pages.js +0 -53
- package/build/request-utils/pages.js.map +0 -1
- package/build/request-utils/patterns.js +0 -27
- package/build/request-utils/patterns.js.map +0 -1
- package/build/request-utils/plugins.js +0 -85
- package/build/request-utils/plugins.js.map +0 -1
- package/build/request-utils/posts.js +0 -42
- package/build/request-utils/posts.js.map +0 -1
- package/build/request-utils/preferences.js +0 -54
- package/build/request-utils/preferences.js.map +0 -1
- package/build/request-utils/records.js.map +0 -1
- package/build/request-utils/rest.js +0 -181
- package/build/request-utils/rest.js.map +0 -1
- package/build/request-utils/site-settings.js +0 -33
- package/build/request-utils/site-settings.js.map +0 -1
- package/build/request-utils/templates.js +0 -54
- package/build/request-utils/templates.js.map +0 -1
- package/build/request-utils/themes.js +0 -69
- package/build/request-utils/themes.js.map +0 -1
- package/build/request-utils/users.js +0 -81
- package/build/request-utils/users.js.map +0 -1
- package/build/request-utils/widgets.js +0 -61
- package/build/request-utils/widgets.js.map +0 -1
- package/build/test.js +0 -189
- package/build/test.js.map +0 -1
- package/build/types.js +0 -3
- package/build/types.js.map +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
|
|
6
|
+
export interface Post {
|
|
7
|
+
id: number;
|
|
8
|
+
content: string;
|
|
9
|
+
status: 'publish' | 'future' | 'draft' | 'pending' | 'private';
|
|
10
|
+
link: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreatePostPayload {
|
|
14
|
+
title?: string;
|
|
15
|
+
content?: string;
|
|
16
|
+
status: 'publish' | 'future' | 'draft' | 'pending' | 'private';
|
|
17
|
+
date?: string;
|
|
18
|
+
date_gmt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Delete all posts using REST API.
|
|
23
|
+
*
|
|
24
|
+
* @param this
|
|
25
|
+
* @param postType The type of post to delete. Defaults to 'posts'.
|
|
26
|
+
*/
|
|
27
|
+
export async function deleteAllPosts(
|
|
28
|
+
this: RequestUtils,
|
|
29
|
+
postType: string = 'posts'
|
|
30
|
+
) {
|
|
31
|
+
// List all posts.
|
|
32
|
+
// https://developer.wordpress.org/rest-api/reference/posts/#list-posts
|
|
33
|
+
const posts = await this.rest< Post[] >( {
|
|
34
|
+
path: `/wp/v2/${ postType }`,
|
|
35
|
+
params: {
|
|
36
|
+
per_page: 100,
|
|
37
|
+
// All possible statuses.
|
|
38
|
+
status: 'publish,future,draft,pending,private,trash',
|
|
39
|
+
},
|
|
40
|
+
} );
|
|
41
|
+
|
|
42
|
+
// Delete all posts one by one.
|
|
43
|
+
// https://developer.wordpress.org/rest-api/reference/posts/#delete-a-post
|
|
44
|
+
// "/wp/v2/posts" not yet supports batch requests.
|
|
45
|
+
await Promise.all(
|
|
46
|
+
posts.map( ( post ) =>
|
|
47
|
+
this.rest( {
|
|
48
|
+
method: 'DELETE',
|
|
49
|
+
path: `/wp/v2/${ postType }/${ post.id }`,
|
|
50
|
+
params: {
|
|
51
|
+
force: true,
|
|
52
|
+
},
|
|
53
|
+
} )
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new post using the REST API.
|
|
60
|
+
*
|
|
61
|
+
* @param this
|
|
62
|
+
* @param payload Post attributes.
|
|
63
|
+
*/
|
|
64
|
+
export async function createPost(
|
|
65
|
+
this: RequestUtils,
|
|
66
|
+
payload: CreatePostPayload
|
|
67
|
+
) {
|
|
68
|
+
return this.createRecord< Post >( 'posts', { ...payload } );
|
|
69
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reset user preferences
|
|
8
|
+
*
|
|
9
|
+
* @param this Request utils.
|
|
10
|
+
*/
|
|
11
|
+
export async function resetPreferences( this: RequestUtils ) {
|
|
12
|
+
await this.rest( {
|
|
13
|
+
path: '/wp/v2/users/me',
|
|
14
|
+
method: 'PUT',
|
|
15
|
+
data: {
|
|
16
|
+
meta: {
|
|
17
|
+
persisted_preferences: {},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
} );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Set scoped user preferences via the REST API.
|
|
25
|
+
*
|
|
26
|
+
* Reads the current persisted preferences, merges the new values under the
|
|
27
|
+
* given scope, and writes them back.
|
|
28
|
+
*
|
|
29
|
+
* @param this Request utils.
|
|
30
|
+
* @param scope The preference scope (e.g. 'core/edit-post').
|
|
31
|
+
* @param preferences Key/value map of preference names to values.
|
|
32
|
+
*/
|
|
33
|
+
export async function setPreferences(
|
|
34
|
+
this: RequestUtils,
|
|
35
|
+
scope: string,
|
|
36
|
+
preferences: Record< string, any >
|
|
37
|
+
) {
|
|
38
|
+
const user = await this.rest< {
|
|
39
|
+
meta: { persisted_preferences: Record< string, any > };
|
|
40
|
+
} >( {
|
|
41
|
+
path: '/wp/v2/users/me',
|
|
42
|
+
method: 'GET',
|
|
43
|
+
params: { context: 'edit' },
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
const currentPreferences = user.meta?.persisted_preferences ?? {};
|
|
47
|
+
|
|
48
|
+
await this.rest( {
|
|
49
|
+
path: '/wp/v2/users/me',
|
|
50
|
+
method: 'PUT',
|
|
51
|
+
data: {
|
|
52
|
+
meta: {
|
|
53
|
+
persisted_preferences: {
|
|
54
|
+
...currentPreferences,
|
|
55
|
+
[ scope ]: {
|
|
56
|
+
...( currentPreferences[ scope ] ?? {} ),
|
|
57
|
+
...preferences,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
} );
|
|
63
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Create a new record at the given REST base path.
|
|
6
8
|
*
|
|
@@ -13,11 +15,14 @@ exports.createRecord = createRecord;
|
|
|
13
15
|
* @param restBase REST base, appended to `/wp/v2/`.
|
|
14
16
|
* @param payload Record attributes.
|
|
15
17
|
*/
|
|
16
|
-
async function createRecord
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
export async function createRecord< T = unknown >(
|
|
19
|
+
this: RequestUtils,
|
|
20
|
+
restBase: string,
|
|
21
|
+
payload: Record< string, unknown >
|
|
22
|
+
) {
|
|
23
|
+
return this.rest< T >( {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
path: `/wp/v2/${ restBase }`,
|
|
26
|
+
data: payload,
|
|
27
|
+
} );
|
|
22
28
|
}
|
|
23
|
-
//# sourceMappingURL=records.js.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
import { expect } from '@playwright/test';
|
|
7
|
+
import type { APIRequestContext } from '@playwright/test';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Internal dependencies
|
|
11
|
+
*/
|
|
12
|
+
import { WP_BASE_URL } from '../config';
|
|
13
|
+
import type { RequestUtils, StorageState } from './index';
|
|
14
|
+
|
|
15
|
+
function splitRequestsToChunks( requests: BatchRequest[], chunkSize: number ) {
|
|
16
|
+
const arr = [ ...requests ];
|
|
17
|
+
const cache = [];
|
|
18
|
+
while ( arr.length ) {
|
|
19
|
+
cache.push( arr.splice( 0, chunkSize ) );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return cache;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function getAPIRootURL( request: APIRequestContext ) {
|
|
26
|
+
// Discover the API root url using link header.
|
|
27
|
+
// See https://developer.wordpress.org/rest-api/using-the-rest-api/discovery/#link-header
|
|
28
|
+
const response = await request.head( WP_BASE_URL );
|
|
29
|
+
const links = response.headers().link;
|
|
30
|
+
const restLink = links?.match( /<([^>]+)>; rel="https:\/\/api\.w\.org\/"/ );
|
|
31
|
+
|
|
32
|
+
if ( ! restLink ) {
|
|
33
|
+
throw new Error( `Failed to discover REST API endpoint.
|
|
34
|
+
Link header: ${ links }` );
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [ , rootURL ] = restLink;
|
|
38
|
+
|
|
39
|
+
return rootURL;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function setupRest( this: RequestUtils ): Promise< StorageState > {
|
|
43
|
+
let nonce = '';
|
|
44
|
+
let rootURL = '';
|
|
45
|
+
|
|
46
|
+
// Poll until the REST API is discovered.
|
|
47
|
+
// See https://github.com/WordPress/gutenberg/issues/61627
|
|
48
|
+
await expect
|
|
49
|
+
.poll(
|
|
50
|
+
async () => {
|
|
51
|
+
try {
|
|
52
|
+
[ nonce, rootURL ] = await Promise.all( [
|
|
53
|
+
this.login(),
|
|
54
|
+
getAPIRootURL( this.request ),
|
|
55
|
+
] );
|
|
56
|
+
} catch ( error ) {
|
|
57
|
+
// Prints the error if the timeout is reached.
|
|
58
|
+
return error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return !! ( nonce && rootURL );
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
message: 'Failed to setup REST API.',
|
|
65
|
+
timeout: 60_000, // 1 minute.
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
.toBe( true );
|
|
69
|
+
|
|
70
|
+
const { cookies } = await this.request.storageState();
|
|
71
|
+
|
|
72
|
+
const storageState: StorageState = {
|
|
73
|
+
cookies,
|
|
74
|
+
nonce,
|
|
75
|
+
rootURL,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if ( this.storageStatePath ) {
|
|
79
|
+
await fs.mkdir( dirname( this.storageStatePath ), { recursive: true } );
|
|
80
|
+
await fs.writeFile(
|
|
81
|
+
this.storageStatePath,
|
|
82
|
+
JSON.stringify( storageState ),
|
|
83
|
+
'utf-8'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.storageState = storageState;
|
|
88
|
+
|
|
89
|
+
return storageState;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type RequestFetchOptions = Exclude<
|
|
93
|
+
Parameters< APIRequestContext[ 'fetch' ] >[ 1 ],
|
|
94
|
+
undefined
|
|
95
|
+
>;
|
|
96
|
+
export interface RestOptions extends RequestFetchOptions {
|
|
97
|
+
path: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function rest< RestResponse = any >(
|
|
101
|
+
this: RequestUtils,
|
|
102
|
+
options: RestOptions
|
|
103
|
+
): Promise< RestResponse > {
|
|
104
|
+
const { path, ...fetchOptions } = options;
|
|
105
|
+
|
|
106
|
+
if ( ! path ) {
|
|
107
|
+
throw new Error( '"path" is required to make a REST call' );
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ( ! this.storageState?.nonce || ! this.storageState?.rootURL ) {
|
|
111
|
+
await this.setupRest();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const relativePath = path.startsWith( '/' ) ? path.slice( 1 ) : path;
|
|
115
|
+
|
|
116
|
+
const url = this.storageState!.rootURL + relativePath;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const response = await this.request.fetch( url, {
|
|
120
|
+
...fetchOptions,
|
|
121
|
+
failOnStatusCode: false,
|
|
122
|
+
headers: {
|
|
123
|
+
'X-WP-Nonce': this.storageState!.nonce,
|
|
124
|
+
...( fetchOptions.headers || {} ),
|
|
125
|
+
},
|
|
126
|
+
} );
|
|
127
|
+
const json: RestResponse = await response.json();
|
|
128
|
+
|
|
129
|
+
if ( ! response.ok() ) {
|
|
130
|
+
throw json;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return json;
|
|
134
|
+
} catch ( error ) {
|
|
135
|
+
// Nonce in invalid, retry again with a renewed nonce.
|
|
136
|
+
if (
|
|
137
|
+
typeof error === 'object' &&
|
|
138
|
+
error !== null &&
|
|
139
|
+
Object.prototype.hasOwnProperty.call( error, 'code' ) &&
|
|
140
|
+
( error as { code: string } ).code === 'rest_cookie_invalid_nonce'
|
|
141
|
+
) {
|
|
142
|
+
await this.setupRest();
|
|
143
|
+
|
|
144
|
+
return this.rest( options );
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get the maximum batch size for the REST API.
|
|
153
|
+
*
|
|
154
|
+
* @param this
|
|
155
|
+
* @param forceRefetch Force revalidate the cached max batch size.
|
|
156
|
+
*/
|
|
157
|
+
async function getMaxBatchSize( this: RequestUtils, forceRefetch = false ) {
|
|
158
|
+
if ( ! forceRefetch && this.maxBatchSize ) {
|
|
159
|
+
return this.maxBatchSize;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const response = await this.rest< {
|
|
163
|
+
endpoints: {
|
|
164
|
+
args: {
|
|
165
|
+
requests: {
|
|
166
|
+
maxItems: number;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}[];
|
|
170
|
+
} >( {
|
|
171
|
+
method: 'OPTIONS',
|
|
172
|
+
path: '/batch/v1',
|
|
173
|
+
} );
|
|
174
|
+
this.maxBatchSize = response.endpoints[ 0 ].args.requests.maxItems;
|
|
175
|
+
return this.maxBatchSize;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface BatchRequest {
|
|
179
|
+
method?: string;
|
|
180
|
+
path: string;
|
|
181
|
+
headers?: Record< string, string | string[] >;
|
|
182
|
+
body?: any;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function batchRest< BatchResponse >(
|
|
186
|
+
this: RequestUtils,
|
|
187
|
+
requests: BatchRequest[]
|
|
188
|
+
): Promise< BatchResponse[] > {
|
|
189
|
+
const maxBatchSize = await this.getMaxBatchSize();
|
|
190
|
+
|
|
191
|
+
if ( requests.length > maxBatchSize ) {
|
|
192
|
+
const chunks = splitRequestsToChunks( requests, maxBatchSize );
|
|
193
|
+
|
|
194
|
+
const chunkResponses = await Promise.all(
|
|
195
|
+
chunks.map( ( chunkRequests ) =>
|
|
196
|
+
this.batchRest< BatchResponse >( chunkRequests )
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return chunkResponses.flat();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const batchResponses = await this.rest< {
|
|
204
|
+
failed?: string;
|
|
205
|
+
responses: BatchResponse[];
|
|
206
|
+
} >( {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
path: '/batch/v1',
|
|
209
|
+
data: {
|
|
210
|
+
requests,
|
|
211
|
+
validation: 'require-all-validate',
|
|
212
|
+
},
|
|
213
|
+
} );
|
|
214
|
+
|
|
215
|
+
if ( batchResponses.failed ) {
|
|
216
|
+
throw batchResponses;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return batchResponses.responses;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { setupRest, rest, getMaxBatchSize, batchRest };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
|
|
6
|
+
type SiteSettings = {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
url: string;
|
|
10
|
+
email: string;
|
|
11
|
+
timezone: string;
|
|
12
|
+
date_format: string;
|
|
13
|
+
time_format: string;
|
|
14
|
+
start_of_week: number;
|
|
15
|
+
language: string;
|
|
16
|
+
use_smilies: boolean;
|
|
17
|
+
default_category: number;
|
|
18
|
+
default_post_format: string;
|
|
19
|
+
posts_per_page: number;
|
|
20
|
+
default_ping_status: 'open' | 'closed';
|
|
21
|
+
default_comment_status: 'open' | 'closed';
|
|
22
|
+
show_on_front: 'posts' | 'page';
|
|
23
|
+
page_on_front: number;
|
|
24
|
+
page_for_posts: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the site settings.
|
|
29
|
+
*
|
|
30
|
+
* @see https://developer.wordpress.org/rest-api/reference/settings/#retrieve-a-site-setting
|
|
31
|
+
*
|
|
32
|
+
* @param this RequestUtils.
|
|
33
|
+
*/
|
|
34
|
+
export async function getSiteSettings( this: RequestUtils ) {
|
|
35
|
+
return await this.rest< SiteSettings >( {
|
|
36
|
+
path: '/wp/v2/settings',
|
|
37
|
+
method: 'GET',
|
|
38
|
+
} );
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Update the site settings.
|
|
43
|
+
*
|
|
44
|
+
* @see https://developer.wordpress.org/rest-api/reference/settings/#update-a-site-setting
|
|
45
|
+
*
|
|
46
|
+
* @param this RequestUtils.
|
|
47
|
+
* @param siteSettings The partial settings payload to update.
|
|
48
|
+
*/
|
|
49
|
+
export async function updateSiteSettings(
|
|
50
|
+
this: RequestUtils,
|
|
51
|
+
siteSettings: Partial< SiteSettings >
|
|
52
|
+
) {
|
|
53
|
+
return await this.rest< SiteSettings >( {
|
|
54
|
+
path: '/wp/v2/settings',
|
|
55
|
+
method: 'POST',
|
|
56
|
+
data: siteSettings,
|
|
57
|
+
} );
|
|
58
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
|
|
6
|
+
type TemplateType = 'wp_template' | 'wp_template_part';
|
|
7
|
+
|
|
8
|
+
interface Template {
|
|
9
|
+
wp_id: number;
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface CreateTemplatePayload {
|
|
14
|
+
slug: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
content?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PATH_MAPPING = {
|
|
21
|
+
wp_template: '/wp/v2/templates',
|
|
22
|
+
wp_template_part: '/wp/v2/template-parts',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Delete all the templates of given type.
|
|
27
|
+
*
|
|
28
|
+
* @param this
|
|
29
|
+
* @param type - Template type to delete.
|
|
30
|
+
*/
|
|
31
|
+
async function deleteAllTemplates( this: RequestUtils, type: TemplateType ) {
|
|
32
|
+
const path = PATH_MAPPING[ type ];
|
|
33
|
+
|
|
34
|
+
if ( ! path ) {
|
|
35
|
+
throw new Error( `Unsupported template type: ${ type }.` );
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const templates = await this.rest< Template[] >( { path } );
|
|
39
|
+
|
|
40
|
+
for ( const template of templates ) {
|
|
41
|
+
if ( ! template?.id || ! template?.wp_id ) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await this.rest( {
|
|
47
|
+
method: 'DELETE',
|
|
48
|
+
path: `${ path }/${ template.id }`,
|
|
49
|
+
params: { force: true },
|
|
50
|
+
} );
|
|
51
|
+
} catch ( responseError ) {
|
|
52
|
+
// Disable reason - the error provides valuable feedback about issues with tests.
|
|
53
|
+
// eslint-disable-next-line no-console
|
|
54
|
+
console.warn(
|
|
55
|
+
`deleteAllTemplates failed to delete template (id: ${ template.wp_id }) with the following error`,
|
|
56
|
+
responseError
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new template using the REST API.
|
|
64
|
+
*
|
|
65
|
+
* @param this
|
|
66
|
+
* @param type Template type to delete.
|
|
67
|
+
* @param payload Template attributes.
|
|
68
|
+
*/
|
|
69
|
+
async function createTemplate(
|
|
70
|
+
this: RequestUtils,
|
|
71
|
+
type: TemplateType,
|
|
72
|
+
payload: CreateTemplatePayload
|
|
73
|
+
) {
|
|
74
|
+
const template = await this.rest< Template >( {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
path: PATH_MAPPING[ type ],
|
|
77
|
+
params: { ...payload, type, status: 'publish', is_wp_suggestion: true },
|
|
78
|
+
} );
|
|
79
|
+
|
|
80
|
+
return template;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { deleteAllTemplates, createTemplate };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestUtils } from './index';
|
|
5
|
+
import { WP_BASE_URL } from '../config';
|
|
6
|
+
|
|
7
|
+
const THEMES_URL = new URL( 'wp-admin/themes.php', WP_BASE_URL ).href;
|
|
8
|
+
|
|
9
|
+
async function activateTheme(
|
|
10
|
+
this: RequestUtils,
|
|
11
|
+
themeSlug: string
|
|
12
|
+
): Promise< void > {
|
|
13
|
+
let response = await this.request.get( THEMES_URL );
|
|
14
|
+
const html = await response.text();
|
|
15
|
+
const optionalFolder = '([a-z0-9-]+%2F)?';
|
|
16
|
+
|
|
17
|
+
// The `optionalFolder` regex part matches paths with a folder,
|
|
18
|
+
// so it will return the first match, which might contain a folder.
|
|
19
|
+
// First try to honor the included theme slug, that is, without a folder.
|
|
20
|
+
let matchGroup = html.match(
|
|
21
|
+
`action=activate&stylesheet=${ encodeURIComponent(
|
|
22
|
+
themeSlug
|
|
23
|
+
) }&_wpnonce=[a-z0-9]+`
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// If the theme is not found, try to match the theme slug with a folder.
|
|
27
|
+
if ( ! matchGroup ) {
|
|
28
|
+
matchGroup = html.match(
|
|
29
|
+
`action=activate&stylesheet=${ optionalFolder }${ encodeURIComponent(
|
|
30
|
+
themeSlug
|
|
31
|
+
) }&_wpnonce=[a-z0-9]+`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if ( ! matchGroup ) {
|
|
36
|
+
if ( html.includes( `data-slug="${ themeSlug }"` ) ) {
|
|
37
|
+
// The theme is already activated.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw new Error( `The theme "${ themeSlug }" is not installed` );
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [ activateQuery ] = matchGroup;
|
|
45
|
+
const activateLink =
|
|
46
|
+
THEMES_URL + `?${ activateQuery }`.replace( /&/g, '&' );
|
|
47
|
+
|
|
48
|
+
response = await this.request.get( activateLink );
|
|
49
|
+
|
|
50
|
+
await response.dispose();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// https://developer.wordpress.org/rest-api/reference/themes/#definition
|
|
54
|
+
async function getCurrentThemeGlobalStylesPostId( this: RequestUtils ) {
|
|
55
|
+
type ThemeItem = {
|
|
56
|
+
stylesheet: string;
|
|
57
|
+
status: string;
|
|
58
|
+
_links: { 'wp:user-global-styles': { href: string }[] };
|
|
59
|
+
};
|
|
60
|
+
const themes = await this.rest< ThemeItem[] >( {
|
|
61
|
+
path: '/wp/v2/themes',
|
|
62
|
+
} );
|
|
63
|
+
let themeGlobalStylesId: string = '';
|
|
64
|
+
if ( themes && themes.length ) {
|
|
65
|
+
const currentTheme: ThemeItem | undefined = themes.find(
|
|
66
|
+
( { status } ) => status === 'active'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const globalStylesURL =
|
|
70
|
+
currentTheme?._links?.[ 'wp:user-global-styles' ]?.[ 0 ]?.href;
|
|
71
|
+
if ( globalStylesURL ) {
|
|
72
|
+
// Extract the ID from the URL. The URL format depends on
|
|
73
|
+
// the permalink structure:
|
|
74
|
+
// - Plain: ?rest_route=/wp/v2/global-styles/123
|
|
75
|
+
// - Pretty: /wp-json/wp/v2/global-styles/123
|
|
76
|
+
const idMatch = globalStylesURL.match(
|
|
77
|
+
/\/wp\/v2\/global-styles\/(\d+)/
|
|
78
|
+
);
|
|
79
|
+
if ( idMatch ) {
|
|
80
|
+
themeGlobalStylesId = idMatch[ 1 ];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return themeGlobalStylesId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Deletes all post revisions using the REST API.
|
|
89
|
+
*
|
|
90
|
+
* @param {} this RequestUtils.
|
|
91
|
+
* @param {string|number} parentId Post attributes.
|
|
92
|
+
*/
|
|
93
|
+
async function getThemeGlobalStylesRevisions(
|
|
94
|
+
this: RequestUtils,
|
|
95
|
+
parentId: number | string
|
|
96
|
+
) {
|
|
97
|
+
// Lists all global styles revisions.
|
|
98
|
+
return await this.rest< Record< string, Object >[] >( {
|
|
99
|
+
path: `/wp/v2/global-styles/${ parentId }/revisions`,
|
|
100
|
+
params: {
|
|
101
|
+
per_page: 100,
|
|
102
|
+
},
|
|
103
|
+
} );
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
activateTheme,
|
|
108
|
+
getCurrentThemeGlobalStylesPostId,
|
|
109
|
+
getThemeGlobalStylesRevisions,
|
|
110
|
+
};
|