@wordpress/api-fetch 7.29.1-next.f34ab90e9.0 → 7.30.1-next.836ecdcae.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 +2 -0
- package/build/index.js +50 -51
- package/build/index.js.map +1 -1
- package/build/middlewares/fetch-all-middleware.js +14 -14
- package/build/middlewares/fetch-all-middleware.js.map +1 -1
- package/build/middlewares/http-v1.js +6 -5
- package/build/middlewares/http-v1.js.map +1 -1
- package/build/middlewares/media-upload.js +7 -7
- package/build/middlewares/media-upload.js.map +1 -1
- package/build/middlewares/namespace-endpoint.js +2 -1
- package/build/middlewares/namespace-endpoint.js.map +1 -1
- package/build/middlewares/nonce.js +7 -5
- package/build/middlewares/nonce.js.map +1 -1
- package/build/middlewares/preloading.js +10 -7
- package/build/middlewares/preloading.js.map +1 -1
- package/build/middlewares/root-url.js +2 -2
- package/build/middlewares/root-url.js.map +1 -1
- package/build/middlewares/theme-preview.js +6 -2
- package/build/middlewares/theme-preview.js.map +1 -1
- package/build/middlewares/user-locale.js +2 -1
- package/build/middlewares/user-locale.js.map +1 -1
- package/build/types.js.map +1 -1
- package/build/utils/response.js +31 -50
- package/build/utils/response.js.map +1 -1
- package/build-module/index.js +38 -52
- package/build-module/index.js.map +1 -1
- package/build-module/middlewares/fetch-all-middleware.js +14 -15
- package/build-module/middlewares/fetch-all-middleware.js.map +1 -1
- package/build-module/middlewares/http-v1.js +6 -5
- package/build-module/middlewares/http-v1.js.map +1 -1
- package/build-module/middlewares/media-upload.js +7 -8
- package/build-module/middlewares/media-upload.js.map +1 -1
- package/build-module/middlewares/namespace-endpoint.js +2 -1
- package/build-module/middlewares/namespace-endpoint.js.map +1 -1
- package/build-module/middlewares/nonce.js +7 -5
- package/build-module/middlewares/nonce.js.map +1 -1
- package/build-module/middlewares/preloading.js +10 -7
- package/build-module/middlewares/preloading.js.map +1 -1
- package/build-module/middlewares/root-url.js +3 -2
- package/build-module/middlewares/root-url.js.map +1 -1
- package/build-module/middlewares/theme-preview.js +5 -2
- package/build-module/middlewares/theme-preview.js.map +1 -1
- package/build-module/middlewares/user-locale.js +2 -1
- package/build-module/middlewares/user-locale.js.map +1 -1
- package/build-module/types.js.map +1 -1
- package/build-module/utils/response.js +30 -48
- package/build-module/utils/response.js.map +1 -1
- package/build-types/index.d.ts +25 -32
- package/build-types/index.d.ts.map +1 -1
- package/build-types/middlewares/fetch-all-middleware.d.ts +5 -4
- package/build-types/middlewares/fetch-all-middleware.d.ts.map +1 -1
- package/build-types/middlewares/http-v1.d.ts +8 -3
- package/build-types/middlewares/http-v1.d.ts.map +1 -1
- package/build-types/middlewares/media-upload.d.ts +5 -4
- package/build-types/middlewares/media-upload.d.ts.map +1 -1
- package/build-types/middlewares/namespace-endpoint.d.ts +4 -3
- package/build-types/middlewares/namespace-endpoint.d.ts.map +1 -1
- package/build-types/middlewares/nonce.d.ts +9 -4
- package/build-types/middlewares/nonce.d.ts.map +1 -1
- package/build-types/middlewares/preloading.d.ts +8 -4
- package/build-types/middlewares/preloading.d.ts.map +1 -1
- package/build-types/middlewares/root-url.d.ts +8 -4
- package/build-types/middlewares/root-url.d.ts.map +1 -1
- package/build-types/middlewares/theme-preview.d.ts +8 -4
- package/build-types/middlewares/theme-preview.d.ts.map +1 -1
- package/build-types/middlewares/user-locale.d.ts +4 -3
- package/build-types/middlewares/user-locale.d.ts.map +1 -1
- package/build-types/types.d.ts +4 -3
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/response.d.ts +13 -5
- package/build-types/utils/response.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/{index.js → index.ts} +78 -69
- package/src/middlewares/{fetch-all-middleware.js → fetch-all-middleware.ts} +24 -20
- package/src/middlewares/{http-v1.js → http-v1.ts} +8 -6
- package/src/middlewares/{media-upload.js → media-upload.ts} +13 -12
- package/src/middlewares/{namespace-endpoint.js → namespace-endpoint.ts} +7 -2
- package/src/middlewares/{nonce.js → nonce.ts} +15 -7
- package/src/middlewares/{preloading.js → preloading.ts} +21 -11
- package/src/middlewares/root-url.ts +47 -0
- package/src/middlewares/test/{fetch-all-middleware.js → fetch-all-middleware.ts} +1 -1
- package/src/middlewares/test/{http-v1.js → http-v1.ts} +7 -4
- package/src/middlewares/test/{media-upload.js → media-upload.ts} +2 -1
- package/src/middlewares/test/{namespace-endpoint.js → namespace-endpoint.ts} +2 -1
- package/src/middlewares/test/{nonce.js → nonce.ts} +5 -4
- package/src/middlewares/test/{preloading.js → preloading.ts} +31 -11
- package/src/middlewares/test/{root-url.js → root-url.ts} +2 -1
- package/src/middlewares/test/{user-locale.js → user-locale.ts} +9 -8
- package/src/middlewares/theme-preview.ts +60 -0
- package/src/middlewares/{user-locale.js → user-locale.ts} +4 -2
- package/src/test/index.js +218 -147
- package/src/types.ts +10 -5
- package/src/utils/response.ts +64 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/middlewares/root-url.js +0 -44
- package/src/middlewares/theme-preview.js +0 -42
- package/src/utils/response.js +0 -85
package/src/test/index.js
CHANGED
|
@@ -1,56 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
import apiFetch from '../';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Mock return value for a successful fetch JSON return value.
|
|
2
|
+
* Mock response value for a successful fetch.
|
|
8
3
|
*
|
|
9
|
-
* @return {
|
|
4
|
+
* @return {Response} Mock return value.
|
|
10
5
|
*/
|
|
11
|
-
const DEFAULT_FETCH_MOCK_RETURN =
|
|
6
|
+
const DEFAULT_FETCH_MOCK_RETURN = {
|
|
7
|
+
ok: true,
|
|
12
8
|
status: 200,
|
|
13
9
|
json: () => Promise.resolve( {} ),
|
|
14
|
-
}
|
|
10
|
+
};
|
|
15
11
|
|
|
16
12
|
describe( 'apiFetch', () => {
|
|
17
|
-
|
|
13
|
+
let apiFetch;
|
|
14
|
+
const originalFetch = globalThis.fetch;
|
|
15
|
+
|
|
16
|
+
beforeEach( async () => {
|
|
17
|
+
// Reset the `apiFetch` module before each test to clear
|
|
18
|
+
// internal variables (middlewares, fetch handler, etc.).
|
|
19
|
+
jest.resetModules();
|
|
20
|
+
apiFetch = ( await import( '../' ) ).default;
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
window.fetch = jest.fn();
|
|
22
|
+
globalThis.fetch = jest.fn();
|
|
21
23
|
} );
|
|
22
24
|
|
|
23
25
|
afterAll( () => {
|
|
24
|
-
|
|
26
|
+
globalThis.fetch = originalFetch;
|
|
25
27
|
} );
|
|
26
28
|
|
|
27
|
-
it( 'should call the API properly', () => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
29
|
+
it( 'should call the API properly', async () => {
|
|
30
|
+
globalThis.fetch.mockResolvedValue( {
|
|
31
|
+
ok: true,
|
|
32
|
+
status: 200,
|
|
33
|
+
async json() {
|
|
34
|
+
return { message: 'ok' };
|
|
35
|
+
},
|
|
36
|
+
} );
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
await expect( apiFetch( { path: '/random' } ) ).resolves.toEqual( {
|
|
39
|
+
message: 'ok',
|
|
39
40
|
} );
|
|
40
41
|
} );
|
|
41
42
|
|
|
42
|
-
it( 'should fetch with non-JSON body', () => {
|
|
43
|
-
|
|
43
|
+
it( 'should fetch with non-JSON body', async () => {
|
|
44
|
+
globalThis.fetch.mockResolvedValue( DEFAULT_FETCH_MOCK_RETURN );
|
|
44
45
|
|
|
45
46
|
const body = 'FormData';
|
|
46
47
|
|
|
47
|
-
apiFetch( {
|
|
48
|
+
await apiFetch( {
|
|
48
49
|
path: '/wp/v2/media',
|
|
49
50
|
method: 'POST',
|
|
50
51
|
body,
|
|
51
52
|
} );
|
|
52
53
|
|
|
53
|
-
expect(
|
|
54
|
+
expect( globalThis.fetch ).toHaveBeenCalledWith(
|
|
54
55
|
'/wp/v2/media?_locale=user',
|
|
55
56
|
{
|
|
56
57
|
credentials: 'include',
|
|
@@ -63,10 +64,10 @@ describe( 'apiFetch', () => {
|
|
|
63
64
|
);
|
|
64
65
|
} );
|
|
65
66
|
|
|
66
|
-
it( 'should fetch with a JSON body', () => {
|
|
67
|
-
|
|
67
|
+
it( 'should fetch with a JSON body', async () => {
|
|
68
|
+
globalThis.fetch.mockResolvedValue( DEFAULT_FETCH_MOCK_RETURN );
|
|
68
69
|
|
|
69
|
-
apiFetch( {
|
|
70
|
+
await apiFetch( {
|
|
70
71
|
path: '/wp/v2/posts',
|
|
71
72
|
method: 'POST',
|
|
72
73
|
headers: {
|
|
@@ -75,7 +76,7 @@ describe( 'apiFetch', () => {
|
|
|
75
76
|
data: {},
|
|
76
77
|
} );
|
|
77
78
|
|
|
78
|
-
expect(
|
|
79
|
+
expect( globalThis.fetch ).toHaveBeenCalledWith(
|
|
79
80
|
'/wp/v2/posts?_locale=user',
|
|
80
81
|
{
|
|
81
82
|
body: '{}',
|
|
@@ -89,17 +90,17 @@ describe( 'apiFetch', () => {
|
|
|
89
90
|
);
|
|
90
91
|
} );
|
|
91
92
|
|
|
92
|
-
it( 'should respect developer-provided options', () => {
|
|
93
|
-
|
|
93
|
+
it( 'should respect developer-provided options', async () => {
|
|
94
|
+
globalThis.fetch.mockResolvedValue( DEFAULT_FETCH_MOCK_RETURN );
|
|
94
95
|
|
|
95
|
-
apiFetch( {
|
|
96
|
+
await apiFetch( {
|
|
96
97
|
path: '/wp/v2/posts',
|
|
97
98
|
method: 'POST',
|
|
98
99
|
data: {},
|
|
99
100
|
credentials: 'omit',
|
|
100
101
|
} );
|
|
101
102
|
|
|
102
|
-
expect(
|
|
103
|
+
expect( globalThis.fetch ).toHaveBeenCalledWith(
|
|
103
104
|
'/wp/v2/posts?_locale=user',
|
|
104
105
|
{
|
|
105
106
|
body: '{}',
|
|
@@ -113,83 +114,86 @@ describe( 'apiFetch', () => {
|
|
|
113
114
|
);
|
|
114
115
|
} );
|
|
115
116
|
|
|
116
|
-
it( 'should return the error message properly', () => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
117
|
+
it( 'should return the error message properly', async () => {
|
|
118
|
+
globalThis.fetch.mockResolvedValue( {
|
|
119
|
+
ok: false,
|
|
120
|
+
status: 400,
|
|
121
|
+
async json() {
|
|
122
|
+
return {
|
|
123
|
+
code: 'bad_request',
|
|
124
|
+
message: 'Bad Request',
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
} );
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
code: 'bad_request',
|
|
133
|
-
message: 'Bad Request',
|
|
134
|
-
} );
|
|
129
|
+
await expect( apiFetch( { path: '/random' } ) ).rejects.toEqual( {
|
|
130
|
+
code: 'bad_request',
|
|
131
|
+
message: 'Bad Request',
|
|
135
132
|
} );
|
|
136
133
|
} );
|
|
137
134
|
|
|
138
|
-
it( 'should return invalid JSON error if no json response', () => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
135
|
+
it( 'should return invalid JSON error if no json response', async () => {
|
|
136
|
+
globalThis.fetch.mockResolvedValue( {
|
|
137
|
+
ok: true,
|
|
138
|
+
status: 200,
|
|
139
|
+
async json() {
|
|
140
|
+
return JSON.parse( '' );
|
|
141
|
+
},
|
|
142
|
+
} );
|
|
144
143
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
code: 'invalid_json',
|
|
149
|
-
message: 'The response is not a valid JSON response.',
|
|
150
|
-
} );
|
|
144
|
+
await expect( apiFetch( { path: '/random' } ) ).rejects.toEqual( {
|
|
145
|
+
code: 'invalid_json',
|
|
146
|
+
message: 'The response is not a valid JSON response.',
|
|
151
147
|
} );
|
|
152
148
|
} );
|
|
153
149
|
|
|
154
|
-
it( 'should return invalid JSON error if response is not valid', () => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
);
|
|
150
|
+
it( 'should return invalid JSON error if response is not valid', async () => {
|
|
151
|
+
globalThis.fetch.mockResolvedValue( {
|
|
152
|
+
ok: true,
|
|
153
|
+
status: 200,
|
|
154
|
+
async json() {
|
|
155
|
+
return JSON.parse( '' );
|
|
156
|
+
},
|
|
157
|
+
} );
|
|
163
158
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
code: 'invalid_json',
|
|
168
|
-
message: 'The response is not a valid JSON response.',
|
|
169
|
-
} );
|
|
159
|
+
await expect( apiFetch( { path: '/random' } ) ).rejects.toEqual( {
|
|
160
|
+
code: 'invalid_json',
|
|
161
|
+
message: 'The response is not a valid JSON response.',
|
|
170
162
|
} );
|
|
171
163
|
} );
|
|
172
164
|
|
|
173
|
-
it( 'should return offline error when fetch errors', () => {
|
|
174
|
-
|
|
165
|
+
it( 'should return offline error when fetch errors', async () => {
|
|
166
|
+
globalThis.fetch.mockRejectedValue(
|
|
167
|
+
new TypeError( 'Failed to fetch' )
|
|
168
|
+
);
|
|
175
169
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
code: 'fetch_error',
|
|
180
|
-
message: 'You are probably offline.',
|
|
181
|
-
} );
|
|
170
|
+
await expect( apiFetch( { path: '/random' } ) ).rejects.toEqual( {
|
|
171
|
+
code: 'fetch_error',
|
|
172
|
+
message: 'Could not get a valid response from the server.',
|
|
182
173
|
} );
|
|
183
174
|
} );
|
|
184
175
|
|
|
185
176
|
it( 'should throw AbortError when fetch aborts', async () => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
177
|
+
globalThis.fetch.mockImplementation(
|
|
178
|
+
( path, { signal } ) =>
|
|
179
|
+
new Promise( ( _, reject ) => {
|
|
180
|
+
if ( ! signal ) {
|
|
181
|
+
reject( new Error( 'Expected signal as argument' ) );
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
signal.throwIfAborted();
|
|
186
|
+
signal.addEventListener(
|
|
187
|
+
'abort',
|
|
188
|
+
( e ) => {
|
|
189
|
+
reject( e.target.reason );
|
|
190
|
+
},
|
|
191
|
+
{ once: true }
|
|
192
|
+
);
|
|
193
|
+
} )
|
|
194
|
+
);
|
|
191
195
|
|
|
192
|
-
const controller = new
|
|
196
|
+
const controller = new globalThis.AbortController();
|
|
193
197
|
|
|
194
198
|
const promise = apiFetch( {
|
|
195
199
|
path: '/random',
|
|
@@ -198,85 +202,149 @@ describe( 'apiFetch', () => {
|
|
|
198
202
|
|
|
199
203
|
controller.abort();
|
|
200
204
|
|
|
201
|
-
|
|
205
|
+
await expect( promise ).rejects.toMatchObject( {
|
|
206
|
+
name: 'AbortError',
|
|
207
|
+
} );
|
|
208
|
+
} );
|
|
209
|
+
|
|
210
|
+
it( 'should return null if response has no content status code', async () => {
|
|
211
|
+
globalThis.fetch.mockResolvedValue( {
|
|
212
|
+
ok: true,
|
|
213
|
+
status: 204,
|
|
214
|
+
} );
|
|
215
|
+
|
|
216
|
+
await expect( apiFetch( { path: '/random' } ) ).resolves.toBe( null );
|
|
217
|
+
} );
|
|
218
|
+
|
|
219
|
+
it( 'should not try to parse the response', async () => {
|
|
220
|
+
const mockResponse = {
|
|
221
|
+
ok: true,
|
|
222
|
+
status: 200,
|
|
223
|
+
};
|
|
202
224
|
|
|
203
|
-
|
|
204
|
-
await promise;
|
|
205
|
-
} catch ( err ) {
|
|
206
|
-
error = err;
|
|
207
|
-
}
|
|
225
|
+
globalThis.fetch.mockResolvedValue( mockResponse );
|
|
208
226
|
|
|
209
|
-
expect(
|
|
227
|
+
await expect(
|
|
228
|
+
apiFetch( { path: '/random', parse: false } )
|
|
229
|
+
).resolves.toBe( mockResponse );
|
|
210
230
|
} );
|
|
211
231
|
|
|
212
|
-
it( 'should
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
232
|
+
it( 'should not try to parse the error', async () => {
|
|
233
|
+
const mockResponse = {
|
|
234
|
+
ok: false,
|
|
235
|
+
status: 400,
|
|
236
|
+
};
|
|
218
237
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
globalThis.fetch.mockResolvedValue( mockResponse );
|
|
239
|
+
|
|
240
|
+
await expect(
|
|
241
|
+
apiFetch( { path: '/random', parse: false } )
|
|
242
|
+
).rejects.toBe( mockResponse );
|
|
223
243
|
} );
|
|
224
244
|
|
|
225
|
-
it( 'should
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
it( 'should refetch after successful nonce refresh', async () => {
|
|
246
|
+
apiFetch.nonceMiddleware =
|
|
247
|
+
apiFetch.createNonceMiddleware( 'old-nonce' );
|
|
248
|
+
apiFetch.use( apiFetch.nonceMiddleware );
|
|
249
|
+
apiFetch.nonceEndpoint = '/rest-nonce';
|
|
250
|
+
|
|
251
|
+
globalThis.fetch.mockImplementation( async ( path, options ) => {
|
|
252
|
+
if ( path.startsWith( '/random' ) ) {
|
|
253
|
+
if ( options?.headers[ 'X-WP-Nonce' ] === 'new-nonce' ) {
|
|
254
|
+
return {
|
|
255
|
+
ok: true,
|
|
256
|
+
status: 200,
|
|
257
|
+
json: async () => ( { code: 'success' } ),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
ok: false,
|
|
263
|
+
status: 403,
|
|
264
|
+
json: async () => ( { code: 'rest_cookie_invalid_nonce' } ),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
231
267
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
268
|
+
if ( path.startsWith( '/rest-nonce' ) ) {
|
|
269
|
+
return {
|
|
270
|
+
ok: true,
|
|
235
271
|
status: 200,
|
|
236
|
-
|
|
272
|
+
text: async () => 'new-nonce',
|
|
273
|
+
};
|
|
237
274
|
}
|
|
238
|
-
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
status: 404,
|
|
279
|
+
json: async () => ( { code: 'rest_no_route' } ),
|
|
280
|
+
};
|
|
281
|
+
} );
|
|
282
|
+
|
|
283
|
+
await expect( apiFetch( { path: '/random' } ) ).resolves.toEqual( {
|
|
284
|
+
code: 'success',
|
|
285
|
+
} );
|
|
239
286
|
} );
|
|
240
287
|
|
|
241
|
-
it( 'should
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
288
|
+
it( 'should fail with rest_cookie_invalid_nonce after failed nonce refresh', async () => {
|
|
289
|
+
apiFetch.nonceMiddleware =
|
|
290
|
+
apiFetch.createNonceMiddleware( 'old-nonce' );
|
|
291
|
+
apiFetch.use( apiFetch.nonceMiddleware );
|
|
292
|
+
apiFetch.nonceEndpoint = '/rest-nonce';
|
|
293
|
+
|
|
294
|
+
globalThis.fetch.mockImplementation( async ( path, options ) => {
|
|
295
|
+
if ( path.startsWith( '/random' ) ) {
|
|
296
|
+
if ( options?.headers[ 'X-WP-Nonce' ] === 'new-nonce' ) {
|
|
297
|
+
return {
|
|
298
|
+
ok: true,
|
|
299
|
+
status: 200,
|
|
300
|
+
json: async () => ( { code: 'success' } ),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
status: 403,
|
|
307
|
+
json: async () => ( { code: 'rest_cookie_invalid_nonce' } ),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
247
310
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
expect( response ).toEqual( {
|
|
311
|
+
if ( path.startsWith( '/rest-nonce' ) ) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
252
314
|
status: 400,
|
|
253
|
-
|
|
315
|
+
text: async () => '0',
|
|
316
|
+
};
|
|
254
317
|
}
|
|
255
|
-
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
status: 404,
|
|
322
|
+
json: async () => ( { code: 'rest_no_route' } ),
|
|
323
|
+
};
|
|
324
|
+
} );
|
|
325
|
+
|
|
326
|
+
await expect( apiFetch( { path: '/random' } ) ).rejects.toEqual( {
|
|
327
|
+
code: 'rest_cookie_invalid_nonce',
|
|
328
|
+
} );
|
|
256
329
|
} );
|
|
257
330
|
|
|
258
|
-
it( 'should not use the default fetch handler when using a custom fetch handler', () => {
|
|
331
|
+
it( 'should not use the default fetch handler when using a custom fetch handler', async () => {
|
|
259
332
|
const customFetchHandler = jest.fn();
|
|
260
333
|
|
|
261
334
|
apiFetch.setFetchHandler( customFetchHandler );
|
|
262
335
|
|
|
263
|
-
apiFetch( { path: '/random' } );
|
|
336
|
+
await apiFetch( { path: '/random' } );
|
|
264
337
|
|
|
265
|
-
expect(
|
|
338
|
+
expect( globalThis.fetch ).not.toHaveBeenCalled();
|
|
266
339
|
|
|
267
340
|
expect( customFetchHandler ).toHaveBeenCalledWith( {
|
|
268
341
|
path: '/random?_locale=user',
|
|
269
342
|
} );
|
|
270
343
|
} );
|
|
271
344
|
|
|
272
|
-
it( 'should run the last-registered user-defined middleware first', () => {
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// to ensure that the last-registered middleware receives the original
|
|
276
|
-
// options object. It also assumes that some built-in middleware would
|
|
277
|
-
// either mutate or clone the original options if the extra middleware
|
|
278
|
-
// had been pushed to the stack.
|
|
279
|
-
expect.assertions( 1 );
|
|
345
|
+
it( 'should run the last-registered user-defined middleware first', async () => {
|
|
346
|
+
// The test assumes that some built-in middleware will either mutate or clone
|
|
347
|
+
// the original options if the extra middleware had been pushed to the stack.
|
|
280
348
|
|
|
281
349
|
const expectedOptions = {};
|
|
282
350
|
|
|
@@ -286,6 +354,9 @@ describe( 'apiFetch', () => {
|
|
|
286
354
|
return next( actualOptions );
|
|
287
355
|
} );
|
|
288
356
|
|
|
289
|
-
|
|
357
|
+
// Set a custom fetch handler to avoid using the default fetch handler.
|
|
358
|
+
apiFetch.setFetchHandler( jest.fn() );
|
|
359
|
+
|
|
360
|
+
await apiFetch( expectedOptions );
|
|
290
361
|
} );
|
|
291
362
|
} );
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export interface APIFetchOptions extends
|
|
1
|
+
export interface APIFetchOptions< Parse extends boolean = boolean >
|
|
2
|
+
extends RequestInit {
|
|
2
3
|
// Override headers, we only accept it as an object due to the `nonce` middleware
|
|
3
4
|
headers?: Record< string, string >;
|
|
4
5
|
path?: string;
|
|
@@ -6,13 +7,17 @@ export interface APIFetchOptions extends RequestInit {
|
|
|
6
7
|
/**
|
|
7
8
|
* @default true
|
|
8
9
|
*/
|
|
9
|
-
parse?:
|
|
10
|
+
parse?: Parse;
|
|
10
11
|
data?: any;
|
|
11
12
|
namespace?: string;
|
|
12
13
|
endpoint?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export type
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
export type FetchHandler< Parse extends boolean = boolean > = (
|
|
17
|
+
nextOptions: APIFetchOptions< Parse >
|
|
18
|
+
) => Promise< any >;
|
|
19
|
+
|
|
20
|
+
export type APIFetchMiddleware< Parse extends boolean = boolean > = (
|
|
21
|
+
options: APIFetchOptions< Parse >,
|
|
22
|
+
next: FetchHandler< Parse >
|
|
18
23
|
) => Promise< any >;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Calls the `json` function on the Response, throwing an error if the response
|
|
8
|
+
* doesn't have a json function or if parsing the json itself fails.
|
|
9
|
+
*
|
|
10
|
+
* @param response
|
|
11
|
+
* @return Parsed response.
|
|
12
|
+
*/
|
|
13
|
+
async function parseJsonAndNormalizeError( response: Response ) {
|
|
14
|
+
try {
|
|
15
|
+
return await response.json();
|
|
16
|
+
} catch {
|
|
17
|
+
throw {
|
|
18
|
+
code: 'invalid_json',
|
|
19
|
+
message: __( 'The response is not a valid JSON response.' ),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parses the apiFetch response properly and normalize response errors.
|
|
26
|
+
*
|
|
27
|
+
* @param response
|
|
28
|
+
* @param shouldParseResponse
|
|
29
|
+
*
|
|
30
|
+
* @return Parsed response.
|
|
31
|
+
*/
|
|
32
|
+
export async function parseResponseAndNormalizeError(
|
|
33
|
+
response: Response,
|
|
34
|
+
shouldParseResponse = true
|
|
35
|
+
) {
|
|
36
|
+
if ( ! shouldParseResponse ) {
|
|
37
|
+
return response;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ( response.status === 204 ) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return await parseJsonAndNormalizeError( response );
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parses a response, throwing an error if parsing the response fails.
|
|
49
|
+
*
|
|
50
|
+
* @param response
|
|
51
|
+
* @param shouldParseResponse
|
|
52
|
+
* @return Never returns, always throws.
|
|
53
|
+
*/
|
|
54
|
+
export async function parseAndThrowError(
|
|
55
|
+
response: Response,
|
|
56
|
+
shouldParseResponse = true
|
|
57
|
+
) {
|
|
58
|
+
if ( ! shouldParseResponse ) {
|
|
59
|
+
throw response;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Parse the response JSON and throw it as an error.
|
|
63
|
+
throw await parseJsonAndNormalizeError( response );
|
|
64
|
+
}
|