@wordpress/core-data 4.0.1 → 4.0.5
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/README.md +13 -7
- package/build/actions.js +178 -122
- package/build/actions.js.map +1 -1
- package/build/batch/default-processor.js +58 -27
- package/build/batch/default-processor.js.map +1 -1
- package/build/entities.js +70 -23
- package/build/entities.js.map +1 -1
- package/build/fetch/__experimental-fetch-url-data.js +1 -1
- package/build/fetch/__experimental-fetch-url-data.js.map +1 -1
- package/build/index.js +9 -17
- package/build/index.js.map +1 -1
- package/build/locks/actions.js +17 -77
- package/build/locks/actions.js.map +1 -1
- package/build/locks/engine.js +77 -0
- package/build/locks/engine.js.map +1 -0
- package/build/locks/reducer.js +1 -5
- package/build/locks/reducer.js.map +1 -1
- package/build/locks/selectors.js +6 -6
- package/build/locks/selectors.js.map +1 -1
- package/build/queried-data/get-query-parts.js +9 -4
- package/build/queried-data/get-query-parts.js.map +1 -1
- package/build/queried-data/selectors.js +3 -9
- package/build/queried-data/selectors.js.map +1 -1
- package/build/reducer.js +17 -22
- package/build/reducer.js.map +1 -1
- package/build/resolvers.js +151 -97
- package/build/resolvers.js.map +1 -1
- package/build/selectors.js +79 -14
- package/build/selectors.js.map +1 -1
- package/build/utils/forward-resolver.js +23 -0
- package/build/utils/forward-resolver.js.map +1 -0
- package/build/utils/index.js +11 -3
- package/build/utils/index.js.map +1 -1
- package/build/utils/is-raw-attribute.js +19 -0
- package/build/utils/is-raw-attribute.js.map +1 -0
- package/build-module/actions.js +155 -112
- package/build-module/actions.js.map +1 -1
- package/build-module/batch/default-processor.js +57 -27
- package/build-module/batch/default-processor.js.map +1 -1
- package/build-module/entities.js +65 -19
- package/build-module/entities.js.map +1 -1
- package/build-module/fetch/__experimental-fetch-url-data.js +1 -1
- package/build-module/fetch/__experimental-fetch-url-data.js.map +1 -1
- package/build-module/index.js +10 -14
- package/build-module/index.js.map +1 -1
- package/build-module/locks/actions.js +14 -68
- package/build-module/locks/actions.js.map +1 -1
- package/build-module/locks/engine.js +66 -0
- package/build-module/locks/engine.js.map +1 -0
- package/build-module/locks/reducer.js +1 -2
- package/build-module/locks/reducer.js.map +1 -1
- package/build-module/locks/selectors.js +4 -4
- package/build-module/locks/selectors.js.map +1 -1
- package/build-module/queried-data/get-query-parts.js +9 -4
- package/build-module/queried-data/get-query-parts.js.map +1 -1
- package/build-module/queried-data/selectors.js +3 -9
- package/build-module/queried-data/selectors.js.map +1 -1
- package/build-module/reducer.js +15 -19
- package/build-module/reducer.js.map +1 -1
- package/build-module/resolvers.js +123 -81
- package/build-module/resolvers.js.map +1 -1
- package/build-module/selectors.js +74 -13
- package/build-module/selectors.js.map +1 -1
- package/build-module/utils/forward-resolver.js +15 -0
- package/build-module/utils/forward-resolver.js.map +1 -0
- package/build-module/utils/index.js +2 -1
- package/build-module/utils/index.js.map +1 -1
- package/build-module/utils/is-raw-attribute.js +12 -0
- package/build-module/utils/is-raw-attribute.js.map +1 -0
- package/package.json +10 -11
- package/src/actions.js +163 -194
- package/src/batch/default-processor.js +57 -26
- package/src/batch/test/default-processor.js +53 -26
- package/src/entities.js +47 -19
- package/src/fetch/__experimental-fetch-url-data.js +1 -1
- package/src/index.js +7 -10
- package/src/locks/actions.js +10 -61
- package/src/locks/engine.js +43 -0
- package/src/locks/reducer.js +1 -3
- package/src/locks/selectors.js +4 -4
- package/src/locks/test/engine.js +135 -0
- package/src/locks/test/reducer.js +1 -1
- package/src/locks/test/selectors.js +105 -124
- package/src/queried-data/get-query-parts.js +11 -6
- package/src/queried-data/selectors.js +2 -9
- package/src/queried-data/test/get-query-parts.js +1 -1
- package/src/queried-data/test/selectors.js +1 -0
- package/src/reducer.js +14 -19
- package/src/resolvers.js +132 -120
- package/src/selectors.js +156 -44
- package/src/test/actions.js +330 -170
- package/src/test/entities.js +40 -26
- package/src/test/resolvers.js +270 -223
- package/src/test/selectors.js +127 -1
- package/src/utils/forward-resolver.js +14 -0
- package/src/utils/index.js +2 -1
- package/src/utils/is-raw-attribute.js +11 -0
- package/src/utils/test/is-raw-attribute.js +22 -0
- package/build/controls.js +0 -44
- package/build/controls.js.map +0 -1
- package/build/locks/index.js +0 -47
- package/build/locks/index.js.map +0 -1
- package/build/utils/if-not-resolved.js +0 -46
- package/build/utils/if-not-resolved.js.map +0 -1
- package/build-module/controls.js +0 -31
- package/build-module/controls.js.map +0 -1
- package/build-module/locks/index.js +0 -4
- package/build-module/locks/index.js.map +0 -1
- package/build-module/utils/if-not-resolved.js +0 -36
- package/build-module/utils/if-not-resolved.js.map +0 -1
- package/src/controls.js +0 -31
- package/src/locks/index.js +0 -3
- package/src/locks/test/actions.js +0 -307
- package/src/test/integration.js +0 -264
- package/src/utils/if-not-resolved.js +0 -40
- package/src/utils/test/if-not-resolved.js +0 -75
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import {
|
|
5
|
-
__unstableAcquireStoreLock,
|
|
6
|
-
__unstableEnqueueLockRequest,
|
|
7
|
-
__unstableReleaseStoreLock,
|
|
8
|
-
__unstableProcessPendingLockRequests,
|
|
9
|
-
} from '../actions';
|
|
10
|
-
|
|
11
|
-
const store = 'test';
|
|
12
|
-
const path = [ 'blue', 'bird' ];
|
|
13
|
-
|
|
14
|
-
describe( '__unstableEnqueueLockRequest', () => {
|
|
15
|
-
it( 'Enqueues a lock request', async () => {
|
|
16
|
-
const fulfillment = __unstableEnqueueLockRequest( store, path, {
|
|
17
|
-
exclusive: true,
|
|
18
|
-
} );
|
|
19
|
-
|
|
20
|
-
// Start
|
|
21
|
-
expect( fulfillment.next().value ).toMatchObject( {
|
|
22
|
-
type: 'ENQUEUE_LOCK_REQUEST',
|
|
23
|
-
request: {
|
|
24
|
-
store,
|
|
25
|
-
path,
|
|
26
|
-
exclusive: true,
|
|
27
|
-
notifyAcquired: expect.any( Function ),
|
|
28
|
-
},
|
|
29
|
-
} );
|
|
30
|
-
|
|
31
|
-
// Should return a promise
|
|
32
|
-
expect( fulfillment.next() ).toMatchObject( {
|
|
33
|
-
done: true,
|
|
34
|
-
value: expect.any( Promise ),
|
|
35
|
-
} );
|
|
36
|
-
} );
|
|
37
|
-
|
|
38
|
-
it( 'Returns a promise fulfilled only after calling notifyAcquired', async () => {
|
|
39
|
-
const fulfillment = __unstableEnqueueLockRequest( store, path, {
|
|
40
|
-
exclusive: true,
|
|
41
|
-
} );
|
|
42
|
-
const { request } = fulfillment.next().value;
|
|
43
|
-
const promise = fulfillment.next().value;
|
|
44
|
-
const fulfilled = jest.fn();
|
|
45
|
-
promise.then( fulfilled );
|
|
46
|
-
// Fulfilled should not be called until notifyAcquired is called
|
|
47
|
-
await sleep( 1 );
|
|
48
|
-
expect( fulfilled ).not.toBeCalled();
|
|
49
|
-
const lock = {};
|
|
50
|
-
request.notifyAcquired( lock );
|
|
51
|
-
expect( fulfilled ).not.toBeCalled();
|
|
52
|
-
|
|
53
|
-
// Promises are resolved only the next tick, so let's wait a little:
|
|
54
|
-
await sleep( 1 );
|
|
55
|
-
expect( fulfilled ).toBeCalledTimes( 1 );
|
|
56
|
-
|
|
57
|
-
// Calling notifyAcquired again shouldn't have any effect:
|
|
58
|
-
request.notifyAcquired( lock );
|
|
59
|
-
await sleep( 1 );
|
|
60
|
-
expect( fulfilled ).toBeCalledTimes( 1 );
|
|
61
|
-
} );
|
|
62
|
-
} );
|
|
63
|
-
|
|
64
|
-
describe( '__unstableProcessPendingLockRequests', () => {
|
|
65
|
-
const exclusive = true;
|
|
66
|
-
const lock = { store, path, exclusive };
|
|
67
|
-
|
|
68
|
-
let notifyAcquired;
|
|
69
|
-
let request;
|
|
70
|
-
|
|
71
|
-
beforeEach( () => {
|
|
72
|
-
notifyAcquired = jest.fn();
|
|
73
|
-
request = { store, path, exclusive, notifyAcquired };
|
|
74
|
-
} );
|
|
75
|
-
|
|
76
|
-
it( 'Grants a lock request that may be granted', async () => {
|
|
77
|
-
const fulfillment = __unstableProcessPendingLockRequests();
|
|
78
|
-
|
|
79
|
-
// Start
|
|
80
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
81
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
// Get pending lock requests
|
|
85
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
86
|
-
|
|
87
|
-
// Find one and check if the request may be granted
|
|
88
|
-
expect( fulfillment.next( [ request ] ).value.type ).toBe(
|
|
89
|
-
'@@data/SELECT'
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// It may, grant it
|
|
93
|
-
expect( fulfillment.next( true ).value.type ).toBe(
|
|
94
|
-
'GRANT_LOCK_REQUEST'
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes
|
|
98
|
-
expect( notifyAcquired ).not.toBeCalled();
|
|
99
|
-
|
|
100
|
-
// All requests processed, return
|
|
101
|
-
expect( fulfillment.next() ).toMatchObject( {
|
|
102
|
-
done: true,
|
|
103
|
-
value: undefined,
|
|
104
|
-
} );
|
|
105
|
-
|
|
106
|
-
// Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes
|
|
107
|
-
expect( notifyAcquired ).toBeCalledWith( lock );
|
|
108
|
-
expect( notifyAcquired ).toBeCalledTimes( 1 );
|
|
109
|
-
|
|
110
|
-
// All requests processed, return
|
|
111
|
-
expect( fulfillment.next() ).toMatchObject( {
|
|
112
|
-
done: true,
|
|
113
|
-
value: undefined,
|
|
114
|
-
} );
|
|
115
|
-
} );
|
|
116
|
-
|
|
117
|
-
it( 'Does not grants a lock request that may not be granted', async () => {
|
|
118
|
-
const fulfillment = __unstableProcessPendingLockRequests();
|
|
119
|
-
|
|
120
|
-
// Start
|
|
121
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
122
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Get pending lock requests
|
|
126
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
127
|
-
|
|
128
|
-
// Find one and check if the request may be granted
|
|
129
|
-
expect( fulfillment.next( [ request ] ).value.type ).toBe(
|
|
130
|
-
'@@data/SELECT'
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// It may not, let's finish
|
|
134
|
-
expect( fulfillment.next( false ) ).toMatchObject( {
|
|
135
|
-
done: true,
|
|
136
|
-
value: undefined,
|
|
137
|
-
} );
|
|
138
|
-
} );
|
|
139
|
-
|
|
140
|
-
it( 'Handles multiple lock requests', async () => {
|
|
141
|
-
const fulfillment = __unstableProcessPendingLockRequests();
|
|
142
|
-
|
|
143
|
-
// Start
|
|
144
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
145
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
// Get pending lock requests
|
|
149
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
150
|
-
|
|
151
|
-
// Find one and check if the request may be granted
|
|
152
|
-
expect( fulfillment.next( [ request, request ] ).value.type ).toBe(
|
|
153
|
-
'@@data/SELECT'
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
// It may not, continue - check if the next one may be granted
|
|
157
|
-
expect( fulfillment.next( false ).value.type ).toBe( '@@data/SELECT' );
|
|
158
|
-
// It may, grant it
|
|
159
|
-
expect( fulfillment.next( true ).value.type ).toBe(
|
|
160
|
-
'GRANT_LOCK_REQUEST'
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
// Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes
|
|
164
|
-
expect( notifyAcquired ).not.toBeCalled();
|
|
165
|
-
|
|
166
|
-
// All requests processed, return
|
|
167
|
-
expect( fulfillment.next() ).toMatchObject( {
|
|
168
|
-
done: true,
|
|
169
|
-
value: undefined,
|
|
170
|
-
} );
|
|
171
|
-
|
|
172
|
-
// Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes
|
|
173
|
-
expect( notifyAcquired ).toBeCalledWith( lock );
|
|
174
|
-
expect( notifyAcquired ).toBeCalledTimes( 1 );
|
|
175
|
-
|
|
176
|
-
// All requests processed, return
|
|
177
|
-
expect( fulfillment.next() ).toMatchObject( {
|
|
178
|
-
done: true,
|
|
179
|
-
value: undefined,
|
|
180
|
-
} );
|
|
181
|
-
} );
|
|
182
|
-
} );
|
|
183
|
-
|
|
184
|
-
describe( '__unstableAcquireStoreLock', () => {
|
|
185
|
-
const exclusive = true;
|
|
186
|
-
const lock = { store, path, exclusive };
|
|
187
|
-
|
|
188
|
-
let notifyAcquired;
|
|
189
|
-
let request;
|
|
190
|
-
|
|
191
|
-
beforeEach( () => {
|
|
192
|
-
notifyAcquired = jest.fn();
|
|
193
|
-
request = { store, path, exclusive, notifyAcquired };
|
|
194
|
-
} );
|
|
195
|
-
|
|
196
|
-
it( 'Enqueues a lock request and attempts to fulfill it', async () => {
|
|
197
|
-
const fulfillment = __unstableAcquireStoreLock( store, path, {
|
|
198
|
-
exclusive,
|
|
199
|
-
} );
|
|
200
|
-
|
|
201
|
-
// Start
|
|
202
|
-
expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' );
|
|
203
|
-
|
|
204
|
-
// Get pending lock requests
|
|
205
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
206
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
207
|
-
);
|
|
208
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
209
|
-
|
|
210
|
-
// Check if lock may be granted
|
|
211
|
-
expect( fulfillment.next( [ request ] ).value.type ).toBe(
|
|
212
|
-
'@@data/SELECT'
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Grant lock request
|
|
216
|
-
expect( fulfillment.next( true ).value.type ).toBe(
|
|
217
|
-
'GRANT_LOCK_REQUEST'
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
// Ensure the promise isn't fulfilled until after GRANT_LOCK_REQUEST finishes
|
|
221
|
-
expect( notifyAcquired ).not.toBeCalled();
|
|
222
|
-
|
|
223
|
-
// Await for lock promise fulfillment
|
|
224
|
-
expect( fulfillment.next( 'promise' ).value.type ).toBe(
|
|
225
|
-
'AWAIT_PROMISE'
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Ensure the promise is fulfilled once GRANT_LOCK_REQUEST finishes
|
|
229
|
-
expect( notifyAcquired ).toBeCalledWith( lock );
|
|
230
|
-
|
|
231
|
-
// Return lock
|
|
232
|
-
expect( fulfillment.next( lock ) ).toMatchObject( {
|
|
233
|
-
done: true,
|
|
234
|
-
value: lock,
|
|
235
|
-
} );
|
|
236
|
-
} );
|
|
237
|
-
|
|
238
|
-
it( 'Enqueues a lock request and waits until fultillment it when not available', async () => {
|
|
239
|
-
const fulfillment = __unstableAcquireStoreLock( store, path, {
|
|
240
|
-
exclusive,
|
|
241
|
-
} );
|
|
242
|
-
|
|
243
|
-
// Start
|
|
244
|
-
expect( fulfillment.next().value.type ).toBe( 'ENQUEUE_LOCK_REQUEST' );
|
|
245
|
-
|
|
246
|
-
// Get pending lock requests
|
|
247
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
248
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
249
|
-
);
|
|
250
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
251
|
-
|
|
252
|
-
// Check if lock may be granted
|
|
253
|
-
expect( fulfillment.next( [ request ] ).value.type ).toBe(
|
|
254
|
-
'@@data/SELECT'
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
// Await until lock request is granted
|
|
258
|
-
expect( fulfillment.next( false ).value.type ).toBe( 'AWAIT_PROMISE' );
|
|
259
|
-
|
|
260
|
-
// Ensure the promise isn't fulfilled at this point...
|
|
261
|
-
expect( notifyAcquired ).not.toBeCalled();
|
|
262
|
-
|
|
263
|
-
await sleep( 1000 );
|
|
264
|
-
|
|
265
|
-
// ...or even a second lateer
|
|
266
|
-
expect( notifyAcquired ).not.toBeCalled();
|
|
267
|
-
|
|
268
|
-
// Let's assume the promise was fulfilled in the end, the action should return
|
|
269
|
-
// the lock once that happens.
|
|
270
|
-
expect( fulfillment.next( lock ) ).toMatchObject( {
|
|
271
|
-
done: true,
|
|
272
|
-
value: lock,
|
|
273
|
-
} );
|
|
274
|
-
} );
|
|
275
|
-
} );
|
|
276
|
-
|
|
277
|
-
describe( '__unstableReleaseStoreLock', () => {
|
|
278
|
-
const lock = { store, path, exclusive: true };
|
|
279
|
-
|
|
280
|
-
it( 'Releases a lock request and attempts to fulfill pending lock requests', async () => {
|
|
281
|
-
const fulfillment = __unstableReleaseStoreLock( lock );
|
|
282
|
-
|
|
283
|
-
// Start
|
|
284
|
-
expect( fulfillment.next().value ).toMatchObject( {
|
|
285
|
-
type: 'RELEASE_LOCK',
|
|
286
|
-
lock,
|
|
287
|
-
} );
|
|
288
|
-
|
|
289
|
-
// Attempt to grant any pending lock requests, find none, return
|
|
290
|
-
expect( fulfillment.next().value.type ).toBe(
|
|
291
|
-
'PROCESS_PENDING_LOCK_REQUESTS'
|
|
292
|
-
);
|
|
293
|
-
expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
|
|
294
|
-
|
|
295
|
-
// Short-circuit with no results and return
|
|
296
|
-
expect( fulfillment.next( [] ) ).toMatchObject( {
|
|
297
|
-
done: true,
|
|
298
|
-
value: undefined,
|
|
299
|
-
} );
|
|
300
|
-
} );
|
|
301
|
-
} );
|
|
302
|
-
|
|
303
|
-
const sleep = ( ms ) => {
|
|
304
|
-
const promise = new Promise( ( resolve ) => setTimeout( resolve, ms ) );
|
|
305
|
-
jest.advanceTimersByTime( ms + 1 );
|
|
306
|
-
return promise;
|
|
307
|
-
};
|
package/src/test/integration.js
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WordPress dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { createRegistry, controls } from '@wordpress/data';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Internal dependencies
|
|
8
|
-
*/
|
|
9
|
-
import * as actions from '../actions';
|
|
10
|
-
import * as selectors from '../selectors';
|
|
11
|
-
import * as resolvers from '../resolvers';
|
|
12
|
-
import { store } from '../';
|
|
13
|
-
|
|
14
|
-
// Mock to prevent calling window.fetch in test environment
|
|
15
|
-
jest.mock( '@wordpress/data-controls', () => {
|
|
16
|
-
const dataControls = jest.requireActual( '@wordpress/data-controls' );
|
|
17
|
-
return {
|
|
18
|
-
...dataControls,
|
|
19
|
-
apiFetch: jest.fn(),
|
|
20
|
-
};
|
|
21
|
-
} );
|
|
22
|
-
const { apiFetch: actualApiFetch } = jest.requireActual(
|
|
23
|
-
'@wordpress/data-controls'
|
|
24
|
-
);
|
|
25
|
-
import { apiFetch } from '@wordpress/data-controls';
|
|
26
|
-
|
|
27
|
-
jest.mock( '@wordpress/api-fetch', () => {
|
|
28
|
-
return {
|
|
29
|
-
__esModule: true,
|
|
30
|
-
default: jest.fn(),
|
|
31
|
-
};
|
|
32
|
-
} );
|
|
33
|
-
import triggerFetch from '@wordpress/api-fetch';
|
|
34
|
-
|
|
35
|
-
const runPromise = async ( promise ) => {
|
|
36
|
-
jest.runAllTimers();
|
|
37
|
-
await promise;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const runPendingPromises = async () => {
|
|
41
|
-
jest.runAllTimers();
|
|
42
|
-
const p = new Promise( ( resolve ) => setTimeout( resolve ) );
|
|
43
|
-
jest.runAllTimers();
|
|
44
|
-
await p;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
describe( 'receiveEntityRecord', () => {
|
|
48
|
-
function createTestRegistry( getEntityRecord ) {
|
|
49
|
-
const registry = createRegistry();
|
|
50
|
-
const initialState = {
|
|
51
|
-
entities: {
|
|
52
|
-
data: {},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
registry.register( store );
|
|
56
|
-
registry.registerStore( 'test/resolution', {
|
|
57
|
-
actions: {
|
|
58
|
-
receiveEntityRecords: actions.receiveEntityRecords,
|
|
59
|
-
*getEntityRecords( ...args ) {
|
|
60
|
-
return yield controls.resolveSelect(
|
|
61
|
-
'test/resolution',
|
|
62
|
-
'getEntityRecords',
|
|
63
|
-
...args
|
|
64
|
-
);
|
|
65
|
-
},
|
|
66
|
-
*getEntityRecord( ...args ) {
|
|
67
|
-
return yield controls.resolveSelect(
|
|
68
|
-
'test/resolution',
|
|
69
|
-
'getEntityRecord',
|
|
70
|
-
...args
|
|
71
|
-
);
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
reducer: ( state = initialState ) => {
|
|
75
|
-
return state;
|
|
76
|
-
},
|
|
77
|
-
selectors: {
|
|
78
|
-
getEntityRecord: selectors.getEntityRecord,
|
|
79
|
-
getEntityRecords: selectors.getEntityRecords,
|
|
80
|
-
},
|
|
81
|
-
resolvers: {
|
|
82
|
-
getEntityRecord,
|
|
83
|
-
getEntityRecords: resolvers.getEntityRecords,
|
|
84
|
-
},
|
|
85
|
-
} );
|
|
86
|
-
return registry;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
beforeEach( async () => {
|
|
90
|
-
apiFetch.mockReset();
|
|
91
|
-
triggerFetch.mockReset();
|
|
92
|
-
jest.useFakeTimers();
|
|
93
|
-
} );
|
|
94
|
-
|
|
95
|
-
it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (default entity key).', async () => {
|
|
96
|
-
const getEntityRecord = jest.fn();
|
|
97
|
-
const registry = createTestRegistry( getEntityRecord );
|
|
98
|
-
|
|
99
|
-
// Trigger resolution of postType records
|
|
100
|
-
apiFetch.mockImplementation( () => ( {
|
|
101
|
-
2: { slug: 'test', id: 2 },
|
|
102
|
-
} ) );
|
|
103
|
-
await runPromise(
|
|
104
|
-
registry
|
|
105
|
-
.dispatch( 'test/resolution' )
|
|
106
|
-
.getEntityRecords( 'root', 'site' )
|
|
107
|
-
);
|
|
108
|
-
jest.runAllTimers();
|
|
109
|
-
|
|
110
|
-
// Select record with id = 2, it is available and should not trigger the resolver
|
|
111
|
-
await runPromise(
|
|
112
|
-
registry
|
|
113
|
-
.dispatch( 'test/resolution' )
|
|
114
|
-
.getEntityRecord( 'root', 'site', 2 )
|
|
115
|
-
);
|
|
116
|
-
expect( getEntityRecord ).not.toHaveBeenCalled();
|
|
117
|
-
|
|
118
|
-
// Select record with id = 4, it is not available and should trigger the resolver
|
|
119
|
-
await runPromise(
|
|
120
|
-
registry
|
|
121
|
-
.dispatch( 'test/resolution' )
|
|
122
|
-
.getEntityRecord( 'root', 'site', 4 )
|
|
123
|
-
);
|
|
124
|
-
expect( getEntityRecord ).toHaveBeenCalled();
|
|
125
|
-
} );
|
|
126
|
-
|
|
127
|
-
it( 'should not trigger a resolver when the requested record is available via receiveEntityRecords (non-default entity key).', async () => {
|
|
128
|
-
const getEntityRecord = jest.fn();
|
|
129
|
-
const registry = createTestRegistry( getEntityRecord );
|
|
130
|
-
|
|
131
|
-
// Trigger resolution of postType records
|
|
132
|
-
apiFetch.mockImplementation( () => ( {
|
|
133
|
-
'test-1': { slug: 'test-1', id: 2 },
|
|
134
|
-
} ) );
|
|
135
|
-
await runPromise(
|
|
136
|
-
registry
|
|
137
|
-
.dispatch( 'test/resolution' )
|
|
138
|
-
.getEntityRecords( 'root', 'taxonomy' )
|
|
139
|
-
);
|
|
140
|
-
jest.runAllTimers();
|
|
141
|
-
|
|
142
|
-
// Select record with id = test-1, it is available and should not trigger the resolver
|
|
143
|
-
await runPromise(
|
|
144
|
-
registry
|
|
145
|
-
.dispatch( 'test/resolution' )
|
|
146
|
-
.getEntityRecord( 'root', 'taxonomy', 'test-1' )
|
|
147
|
-
);
|
|
148
|
-
expect( getEntityRecord ).not.toHaveBeenCalled();
|
|
149
|
-
|
|
150
|
-
// Select record with id = test-2, it is not available and should trigger the resolver
|
|
151
|
-
await runPromise(
|
|
152
|
-
registry
|
|
153
|
-
.dispatch( 'test/resolution' )
|
|
154
|
-
.getEntityRecord( 'root', 'taxonomy', 'test-2' )
|
|
155
|
-
);
|
|
156
|
-
expect( getEntityRecord ).toHaveBeenCalled();
|
|
157
|
-
} );
|
|
158
|
-
} );
|
|
159
|
-
|
|
160
|
-
describe( 'saveEntityRecord', () => {
|
|
161
|
-
function createTestRegistry() {
|
|
162
|
-
const registry = createRegistry();
|
|
163
|
-
registry.register( store );
|
|
164
|
-
return registry;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
beforeEach( async () => {
|
|
168
|
-
apiFetch.mockReset();
|
|
169
|
-
triggerFetch.mockReset();
|
|
170
|
-
jest.useFakeTimers( 'modern' );
|
|
171
|
-
} );
|
|
172
|
-
|
|
173
|
-
it( 'should not trigger any GET requests until POST/PUT is finished.', async () => {
|
|
174
|
-
const registry = createTestRegistry();
|
|
175
|
-
// Fetch post types from the API {{{
|
|
176
|
-
apiFetch.mockImplementation( () => ( {
|
|
177
|
-
'post-1': { slug: 'post-1' },
|
|
178
|
-
} ) );
|
|
179
|
-
|
|
180
|
-
// Trigger fetch
|
|
181
|
-
registry.select( 'core' ).getEntityRecords( 'root', 'postType' );
|
|
182
|
-
jest.runAllTimers();
|
|
183
|
-
await Promise.resolve().then( () => jest.runAllTimers() );
|
|
184
|
-
expect( apiFetch ).toBeCalledTimes( 1 );
|
|
185
|
-
expect( apiFetch ).toBeCalledWith( {
|
|
186
|
-
path: '/wp/v2/types?context=edit',
|
|
187
|
-
} );
|
|
188
|
-
|
|
189
|
-
// Select fetched results, there should be no subsequent request
|
|
190
|
-
apiFetch.mockReset();
|
|
191
|
-
const results = registry
|
|
192
|
-
.select( 'core' )
|
|
193
|
-
.getEntityRecords( 'root', 'postType' );
|
|
194
|
-
expect( apiFetch ).toBeCalledTimes( 0 );
|
|
195
|
-
jest.runAllTimers();
|
|
196
|
-
expect( apiFetch ).toBeCalledTimes( 0 );
|
|
197
|
-
expect( results ).toHaveLength( 1 );
|
|
198
|
-
expect( results[ 0 ].slug ).toBe( 'post-1' );
|
|
199
|
-
// }}} Fetch post types from the API
|
|
200
|
-
|
|
201
|
-
// Save changes
|
|
202
|
-
apiFetch.mockClear();
|
|
203
|
-
apiFetch.mockImplementation( actualApiFetch );
|
|
204
|
-
let resolvePromise;
|
|
205
|
-
triggerFetch.mockImplementation( function () {
|
|
206
|
-
return new Promise( ( resolve ) => {
|
|
207
|
-
resolvePromise = resolve;
|
|
208
|
-
} );
|
|
209
|
-
} );
|
|
210
|
-
const savePromise = registry
|
|
211
|
-
.dispatch( 'core' )
|
|
212
|
-
.saveEntityRecord( 'root', 'postType', {
|
|
213
|
-
slug: 'post-1',
|
|
214
|
-
newField: 'a',
|
|
215
|
-
} );
|
|
216
|
-
await runPendingPromises();
|
|
217
|
-
|
|
218
|
-
// There should ONLY be a single hanging API call (PUT) by this point.
|
|
219
|
-
// If there have been any other requests, it is a race condition of some sorts,
|
|
220
|
-
// e.g. a resolution was triggered before the save was finished.
|
|
221
|
-
expect( triggerFetch ).toBeCalledTimes( 1 );
|
|
222
|
-
expect( triggerFetch ).toHaveBeenCalledWith(
|
|
223
|
-
expect.objectContaining( {
|
|
224
|
-
method: 'PUT',
|
|
225
|
-
path: '/wp/v2/types/post-1',
|
|
226
|
-
data: expect.objectContaining( {
|
|
227
|
-
newField: 'a',
|
|
228
|
-
slug: 'post-1',
|
|
229
|
-
} ),
|
|
230
|
-
} )
|
|
231
|
-
);
|
|
232
|
-
triggerFetch.mockClear();
|
|
233
|
-
apiFetch.mockClear();
|
|
234
|
-
|
|
235
|
-
// The PUT is still hanging, let's call a selector now and make sure it won't trigger
|
|
236
|
-
// any requests
|
|
237
|
-
registry.select( 'core' ).getEntityRecords( 'root', 'postType' );
|
|
238
|
-
jest.runAllTimers();
|
|
239
|
-
expect( triggerFetch ).toBeCalledTimes( 0 );
|
|
240
|
-
|
|
241
|
-
// Now that all timers are exhausted, let's resolve the PUT request and wait until the
|
|
242
|
-
// save is complete
|
|
243
|
-
resolvePromise( { newField: 'a', slug: 'post-1' } );
|
|
244
|
-
|
|
245
|
-
// Run selector and make sure it doesn't trigger any requests just yet
|
|
246
|
-
registry.select( 'core' ).getEntityRecords( 'root', 'postType' );
|
|
247
|
-
jest.runAllTimers();
|
|
248
|
-
expect( triggerFetch ).toBeCalledTimes( 0 );
|
|
249
|
-
|
|
250
|
-
const newRecord = await savePromise;
|
|
251
|
-
expect( newRecord ).toEqual( { newField: 'a', slug: 'post-1' } );
|
|
252
|
-
// There should be no other API calls just because saving succeeded
|
|
253
|
-
jest.runAllTimers();
|
|
254
|
-
expect( triggerFetch ).toBeCalledTimes( 0 );
|
|
255
|
-
|
|
256
|
-
// Calling the selector after the save is finished should trigger a resolver and a GET request
|
|
257
|
-
registry.select( 'core' ).getEntityRecords( 'root', 'postType' );
|
|
258
|
-
await runPendingPromises();
|
|
259
|
-
expect( triggerFetch ).toBeCalledTimes( 1 );
|
|
260
|
-
expect( triggerFetch ).toBeCalledWith( {
|
|
261
|
-
path: '/wp/v2/types?context=edit',
|
|
262
|
-
} );
|
|
263
|
-
} );
|
|
264
|
-
} );
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WordPress dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { controls } from '@wordpress/data';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Internal dependencies
|
|
8
|
-
*/
|
|
9
|
-
import { STORE_NAME } from '../name';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Higher-order function which invokes the given resolver only if it has not
|
|
13
|
-
* already been resolved with the arguments passed to the enhanced function.
|
|
14
|
-
*
|
|
15
|
-
* This only considers resolution state, and notably does not support resolver
|
|
16
|
-
* custom `isFulfilled` behavior.
|
|
17
|
-
*
|
|
18
|
-
* @param {Function} resolver Original resolver.
|
|
19
|
-
* @param {string} selectorName Selector name associated with resolver.
|
|
20
|
-
*
|
|
21
|
-
* @return {Function} Enhanced resolver.
|
|
22
|
-
*/
|
|
23
|
-
const ifNotResolved = ( resolver, selectorName ) =>
|
|
24
|
-
/**
|
|
25
|
-
* @param {...any} args Original resolver arguments.
|
|
26
|
-
*/
|
|
27
|
-
function* resolveIfNotResolved( ...args ) {
|
|
28
|
-
const hasStartedResolution = yield controls.select(
|
|
29
|
-
STORE_NAME,
|
|
30
|
-
'hasStartedResolution',
|
|
31
|
-
selectorName,
|
|
32
|
-
args
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
if ( ! hasStartedResolution ) {
|
|
36
|
-
yield* resolver( ...args );
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export default ifNotResolved;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WordPress dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { controls } from '@wordpress/data';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Internal dependencies
|
|
8
|
-
*/
|
|
9
|
-
import ifNotResolved from '../if-not-resolved';
|
|
10
|
-
|
|
11
|
-
jest.mock( '@wordpress/data', () => ( {
|
|
12
|
-
controls: {
|
|
13
|
-
select: jest.fn(),
|
|
14
|
-
},
|
|
15
|
-
} ) );
|
|
16
|
-
|
|
17
|
-
describe( 'ifNotResolved', () => {
|
|
18
|
-
beforeEach( () => {
|
|
19
|
-
controls.select.mockReset();
|
|
20
|
-
} );
|
|
21
|
-
|
|
22
|
-
it( 'returns a new function', () => {
|
|
23
|
-
const originalResolver = () => {};
|
|
24
|
-
|
|
25
|
-
const resolver = ifNotResolved( originalResolver, 'originalResolver' );
|
|
26
|
-
|
|
27
|
-
expect( resolver ).toBeInstanceOf( Function );
|
|
28
|
-
} );
|
|
29
|
-
|
|
30
|
-
it( 'triggers original resolver if not already resolved', () => {
|
|
31
|
-
controls.select.mockImplementation( ( _storeKey, selectorName ) => ( {
|
|
32
|
-
_nextValue:
|
|
33
|
-
selectorName === 'hasStartedResolution' ? false : undefined,
|
|
34
|
-
} ) );
|
|
35
|
-
|
|
36
|
-
const originalResolver = jest
|
|
37
|
-
.fn()
|
|
38
|
-
.mockImplementation( function* () {} );
|
|
39
|
-
|
|
40
|
-
const resolver = ifNotResolved( originalResolver, 'originalResolver' );
|
|
41
|
-
|
|
42
|
-
const runResolver = resolver();
|
|
43
|
-
|
|
44
|
-
let next, nextValue;
|
|
45
|
-
do {
|
|
46
|
-
next = runResolver.next( nextValue );
|
|
47
|
-
nextValue = next.value?._nextValue;
|
|
48
|
-
} while ( ! next.done );
|
|
49
|
-
|
|
50
|
-
expect( originalResolver ).toHaveBeenCalledTimes( 1 );
|
|
51
|
-
} );
|
|
52
|
-
|
|
53
|
-
it( 'does not trigger original resolver if already resolved', () => {
|
|
54
|
-
controls.select.mockImplementation( ( _storeKey, selectorName ) => ( {
|
|
55
|
-
_nextValue:
|
|
56
|
-
selectorName === 'hasStartedResolution' ? true : undefined,
|
|
57
|
-
} ) );
|
|
58
|
-
|
|
59
|
-
const originalResolver = jest
|
|
60
|
-
.fn()
|
|
61
|
-
.mockImplementation( function* () {} );
|
|
62
|
-
|
|
63
|
-
const resolver = ifNotResolved( originalResolver, 'originalResolver' );
|
|
64
|
-
|
|
65
|
-
const runResolver = resolver();
|
|
66
|
-
|
|
67
|
-
let next, nextValue;
|
|
68
|
-
do {
|
|
69
|
-
next = runResolver.next( nextValue );
|
|
70
|
-
nextValue = next.value?._nextValue;
|
|
71
|
-
} while ( ! next.done );
|
|
72
|
-
|
|
73
|
-
expect( originalResolver ).toHaveBeenCalledTimes( 0 );
|
|
74
|
-
} );
|
|
75
|
-
} );
|