@wordpress/api-fetch 7.29.1-next.e256d081a.0 → 7.30.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.
@@ -6,13 +6,13 @@
6
6
  *
7
7
  * @return Parsed response.
8
8
  */
9
- export declare const parseResponseAndNormalizeError: (response: Response, shouldParseResponse?: boolean) => Promise<any>;
9
+ export declare function parseResponseAndNormalizeError(response: Response, shouldParseResponse?: boolean): Promise<any>;
10
10
  /**
11
11
  * Parses a response, throwing an error if parsing the response fails.
12
12
  *
13
13
  * @param response
14
14
  * @param shouldParseResponse
15
- * @return Parsed response.
15
+ * @return Never returns, always throws.
16
16
  */
17
- export declare function parseAndThrowError(response: Response, shouldParseResponse?: boolean): Promise<never>;
17
+ export declare function parseAndThrowError(response: Response, shouldParseResponse?: boolean): Promise<void>;
18
18
  //# sourceMappingURL=response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":"AA+CA;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,aAChC,QAAQ,gDAMlB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,QAAQ,EAClB,mBAAmB,UAAO,kBAc1B"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":"AAuBA;;;;;;;GAOG;AACH,wBAAsB,8BAA8B,CACnD,QAAQ,EAAE,QAAQ,EAClB,mBAAmB,UAAO,gBAW1B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACvC,QAAQ,EAAE,QAAQ,EAClB,mBAAmB,UAAO,iBAQ1B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/api-fetch",
3
- "version": "7.29.1-next.e256d081a.0",
3
+ "version": "7.30.0",
4
4
  "description": "Utility to make WordPress REST API requests.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -30,11 +30,11 @@
30
30
  "types": "build-types",
31
31
  "dependencies": {
32
32
  "@babel/runtime": "7.25.7",
33
- "@wordpress/i18n": "^6.2.1-next.e256d081a.0",
34
- "@wordpress/url": "^4.29.1-next.e256d081a.0"
33
+ "@wordpress/i18n": "^6.3.0",
34
+ "@wordpress/url": "^4.30.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "gitHead": "7d529ba9a461795d5f1572d3856de33f744287c2"
39
+ "gitHead": "c66cb089eed19d4f4956962fa7b4c81abe6dd513"
40
40
  }
package/src/index.ts CHANGED
@@ -61,21 +61,6 @@ function registerMiddleware( middleware: APIFetchMiddleware ) {
61
61
  middlewares.unshift( middleware );
62
62
  }
63
63
 
64
- /**
65
- * Checks the status of a response, throwing the Response as an error if
66
- * it is outside the 200 range.
67
- *
68
- * @param response
69
- * @return The response if the status is in the 200 range.
70
- */
71
- const checkStatus = ( response: Response ) => {
72
- if ( response.status >= 200 && response.status < 300 ) {
73
- return response;
74
- }
75
-
76
- throw response;
77
- };
78
-
79
64
  const defaultFetchHandler: FetchHandler = ( nextOptions ) => {
80
65
  const { url, path, data, parse = true, ...remainingOptions } = nextOptions;
81
66
  let { body, headers } = nextOptions;
@@ -89,7 +74,7 @@ const defaultFetchHandler: FetchHandler = ( nextOptions ) => {
89
74
  headers[ 'Content-Type' ] = 'application/json';
90
75
  }
91
76
 
92
- const responsePromise = window.fetch(
77
+ const responsePromise = globalThis.fetch(
93
78
  // Fall back to explicitly passing `window.location` which is the behavior if `undefined` is passed.
94
79
  url || path || window.location.href,
95
80
  {
@@ -101,24 +86,38 @@ const defaultFetchHandler: FetchHandler = ( nextOptions ) => {
101
86
  );
102
87
 
103
88
  return responsePromise.then(
104
- ( value ) =>
105
- Promise.resolve( value )
106
- .then( checkStatus )
107
- .catch( ( response ) => parseAndThrowError( response, parse ) )
108
- .then( ( response ) =>
109
- parseResponseAndNormalizeError( response, parse )
110
- ),
89
+ ( response ) => {
90
+ // If the response is not 2xx, still parse the response body as JSON
91
+ // but throw the JSON as error.
92
+ if ( ! response.ok ) {
93
+ return parseAndThrowError( response, parse );
94
+ }
95
+
96
+ return parseResponseAndNormalizeError( response, parse );
97
+ },
111
98
  ( err ) => {
112
99
  // Re-throw AbortError for the users to handle it themselves.
113
100
  if ( err && err.name === 'AbortError' ) {
114
101
  throw err;
115
102
  }
116
103
 
117
- // Otherwise, there is most likely no network connection.
118
- // Unfortunately the message might depend on the browser.
104
+ // If the browser reports being offline, we'll just assume that
105
+ // this is why the request failed.
106
+ if ( ! globalThis.navigator.onLine ) {
107
+ throw {
108
+ code: 'offline_error',
109
+ message: __(
110
+ 'Unable to connect. Please check your Internet connection.'
111
+ ),
112
+ };
113
+ }
114
+
115
+ // Hard to diagnose further due to how Window.fetch reports errors.
119
116
  throw {
120
117
  code: 'fetch_error',
121
- message: __( 'You are probably offline.' ),
118
+ message: __(
119
+ 'Could not get a valid response from the server.'
120
+ ),
122
121
  };
123
122
  }
124
123
  );
@@ -177,10 +176,17 @@ const apiFetch: apiFetch = ( options ) => {
177
176
  }
178
177
 
179
178
  // If the nonce is invalid, refresh it and try again.
180
- return window
179
+ return globalThis
181
180
  .fetch( apiFetch.nonceEndpoint! )
182
- .then( checkStatus )
183
- .then( ( data ) => data.text() )
181
+ .then( ( response ) => {
182
+ // If the nonce refresh fails, it means we failed to recover from the original
183
+ // `rest_cookie_invalid_nonce` error and that it's time to finally re-throw it.
184
+ if ( ! response.ok ) {
185
+ return Promise.reject( error );
186
+ }
187
+
188
+ return response.text();
189
+ } )
184
190
  .then( ( text ) => {
185
191
  apiFetch.nonceMiddleware!.nonce = text;
186
192
  return apiFetch( options );
@@ -63,9 +63,9 @@ const mediaUploadMiddleware: APIFetchMiddleware = ( options, next ) => {
63
63
  };
64
64
 
65
65
  return next( { ...options, parse: false } )
66
- .catch( ( response ) => {
66
+ .catch( ( response: Response ) => {
67
67
  // `response` could actually be an error thrown by `defaultFetchHandler`.
68
- if ( ! response.headers ) {
68
+ if ( ! ( response instanceof globalThis.Response ) ) {
69
69
  return Promise.reject( response );
70
70
  }
71
71
 
@@ -92,7 +92,7 @@ const mediaUploadMiddleware: APIFetchMiddleware = ( options, next ) => {
92
92
  }
93
93
  return parseAndThrowError( response, options.parse );
94
94
  } )
95
- .then( ( response ) =>
95
+ .then( ( response: Response ) =>
96
96
  parseResponseAndNormalizeError( response, options.parse )
97
97
  );
98
98
  };
package/src/test/index.js CHANGED
@@ -1,56 +1,57 @@
1
1
  /**
2
- * Internal dependencies
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 {Promise} Mock return value.
4
+ * @return {Response} Mock return value.
10
5
  */
11
- const DEFAULT_FETCH_MOCK_RETURN = Promise.resolve( {
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
- const originalFetch = window.fetch;
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
- beforeEach( () => {
20
- window.fetch = jest.fn();
22
+ globalThis.fetch = jest.fn();
21
23
  } );
22
24
 
23
25
  afterAll( () => {
24
- window.fetch = originalFetch;
26
+ globalThis.fetch = originalFetch;
25
27
  } );
26
28
 
27
- it( 'should call the API properly', () => {
28
- window.fetch.mockReturnValue(
29
- Promise.resolve( {
30
- status: 200,
31
- json() {
32
- return Promise.resolve( { message: 'ok' } );
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
- return apiFetch( { path: '/random' } ).then( ( body ) => {
38
- expect( body ).toEqual( { message: 'ok' } );
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
- window.fetch.mockReturnValue( DEFAULT_FETCH_MOCK_RETURN );
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( window.fetch ).toHaveBeenCalledWith(
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
- window.fetch.mockReturnValue( DEFAULT_FETCH_MOCK_RETURN );
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( window.fetch ).toHaveBeenCalledWith(
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
- window.fetch.mockReturnValue( DEFAULT_FETCH_MOCK_RETURN );
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( window.fetch ).toHaveBeenCalledWith(
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
- window.fetch.mockReturnValue(
118
- Promise.resolve( {
119
- status: 400,
120
- json() {
121
- return Promise.resolve( {
122
- code: 'bad_request',
123
- message: 'Bad Request',
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
- return apiFetch( { path: '/random' } ).catch( ( body ) => {
130
- // eslint-disable-next-line jest/no-conditional-expect
131
- expect( body ).toEqual( {
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
- window.fetch.mockReturnValue(
140
- Promise.resolve( {
141
- status: 200,
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
- return apiFetch( { path: '/random' } ).catch( ( body ) => {
146
- // eslint-disable-next-line jest/no-conditional-expect
147
- expect( body ).toEqual( {
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
- window.fetch.mockReturnValue(
156
- Promise.resolve( {
157
- status: 200,
158
- json() {
159
- return Promise.reject();
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
- return apiFetch( { path: '/random' } ).catch( ( body ) => {
165
- // eslint-disable-next-line jest/no-conditional-expect
166
- expect( body ).toEqual( {
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
- window.fetch.mockReturnValue( Promise.reject() );
165
+ it( 'should return offline error when fetch errors', async () => {
166
+ globalThis.fetch.mockRejectedValue(
167
+ new TypeError( 'Failed to fetch' )
168
+ );
175
169
 
176
- return apiFetch( { path: '/random' } ).catch( ( body ) => {
177
- // eslint-disable-next-line jest/no-conditional-expect
178
- expect( body ).toEqual( {
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
- const abortError = new Error();
187
- abortError.name = 'AbortError';
188
- abortError.code = 20;
189
-
190
- window.fetch.mockReturnValue( Promise.reject( abortError ) );
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 window.AbortController();
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
- let error;
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
- try {
204
- await promise;
205
- } catch ( err ) {
206
- error = err;
207
- }
225
+ globalThis.fetch.mockResolvedValue( mockResponse );
208
226
 
209
- expect( error.name ).toBe( 'AbortError' );
227
+ await expect(
228
+ apiFetch( { path: '/random', parse: false } )
229
+ ).resolves.toBe( mockResponse );
210
230
  } );
211
231
 
212
- it( 'should return null if response has no content status code', () => {
213
- window.fetch.mockReturnValue(
214
- Promise.resolve( {
215
- status: 204,
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
- return apiFetch( { path: '/random' } ).catch( ( body ) => {
220
- // eslint-disable-next-line jest/no-conditional-expect
221
- expect( body ).toEqual( null );
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 not try to parse the response', () => {
226
- window.fetch.mockReturnValue(
227
- Promise.resolve( {
228
- status: 200,
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
- return apiFetch( { path: '/random', parse: false } ).then(
233
- ( response ) => {
234
- expect( response ).toEqual( {
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 not try to parse the error', () => {
242
- window.fetch.mockReturnValue(
243
- Promise.resolve( {
244
- status: 400,
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
- return apiFetch( { path: '/random', parse: false } ).catch(
249
- ( response ) => {
250
- // eslint-disable-next-line jest/no-conditional-expect
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( window.fetch ).not.toHaveBeenCalled();
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
- // This could potentially impact other tests in that a lingering
274
- // middleware is left. For the purposes of this test, it is sufficient
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
- apiFetch( expectedOptions );
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
  } );