@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.
Files changed (116) hide show
  1. package/README.md +13 -7
  2. package/build/actions.js +178 -122
  3. package/build/actions.js.map +1 -1
  4. package/build/batch/default-processor.js +58 -27
  5. package/build/batch/default-processor.js.map +1 -1
  6. package/build/entities.js +70 -23
  7. package/build/entities.js.map +1 -1
  8. package/build/fetch/__experimental-fetch-url-data.js +1 -1
  9. package/build/fetch/__experimental-fetch-url-data.js.map +1 -1
  10. package/build/index.js +9 -17
  11. package/build/index.js.map +1 -1
  12. package/build/locks/actions.js +17 -77
  13. package/build/locks/actions.js.map +1 -1
  14. package/build/locks/engine.js +77 -0
  15. package/build/locks/engine.js.map +1 -0
  16. package/build/locks/reducer.js +1 -5
  17. package/build/locks/reducer.js.map +1 -1
  18. package/build/locks/selectors.js +6 -6
  19. package/build/locks/selectors.js.map +1 -1
  20. package/build/queried-data/get-query-parts.js +9 -4
  21. package/build/queried-data/get-query-parts.js.map +1 -1
  22. package/build/queried-data/selectors.js +3 -9
  23. package/build/queried-data/selectors.js.map +1 -1
  24. package/build/reducer.js +17 -22
  25. package/build/reducer.js.map +1 -1
  26. package/build/resolvers.js +151 -97
  27. package/build/resolvers.js.map +1 -1
  28. package/build/selectors.js +79 -14
  29. package/build/selectors.js.map +1 -1
  30. package/build/utils/forward-resolver.js +23 -0
  31. package/build/utils/forward-resolver.js.map +1 -0
  32. package/build/utils/index.js +11 -3
  33. package/build/utils/index.js.map +1 -1
  34. package/build/utils/is-raw-attribute.js +19 -0
  35. package/build/utils/is-raw-attribute.js.map +1 -0
  36. package/build-module/actions.js +155 -112
  37. package/build-module/actions.js.map +1 -1
  38. package/build-module/batch/default-processor.js +57 -27
  39. package/build-module/batch/default-processor.js.map +1 -1
  40. package/build-module/entities.js +65 -19
  41. package/build-module/entities.js.map +1 -1
  42. package/build-module/fetch/__experimental-fetch-url-data.js +1 -1
  43. package/build-module/fetch/__experimental-fetch-url-data.js.map +1 -1
  44. package/build-module/index.js +10 -14
  45. package/build-module/index.js.map +1 -1
  46. package/build-module/locks/actions.js +14 -68
  47. package/build-module/locks/actions.js.map +1 -1
  48. package/build-module/locks/engine.js +66 -0
  49. package/build-module/locks/engine.js.map +1 -0
  50. package/build-module/locks/reducer.js +1 -2
  51. package/build-module/locks/reducer.js.map +1 -1
  52. package/build-module/locks/selectors.js +4 -4
  53. package/build-module/locks/selectors.js.map +1 -1
  54. package/build-module/queried-data/get-query-parts.js +9 -4
  55. package/build-module/queried-data/get-query-parts.js.map +1 -1
  56. package/build-module/queried-data/selectors.js +3 -9
  57. package/build-module/queried-data/selectors.js.map +1 -1
  58. package/build-module/reducer.js +15 -19
  59. package/build-module/reducer.js.map +1 -1
  60. package/build-module/resolvers.js +123 -81
  61. package/build-module/resolvers.js.map +1 -1
  62. package/build-module/selectors.js +74 -13
  63. package/build-module/selectors.js.map +1 -1
  64. package/build-module/utils/forward-resolver.js +15 -0
  65. package/build-module/utils/forward-resolver.js.map +1 -0
  66. package/build-module/utils/index.js +2 -1
  67. package/build-module/utils/index.js.map +1 -1
  68. package/build-module/utils/is-raw-attribute.js +12 -0
  69. package/build-module/utils/is-raw-attribute.js.map +1 -0
  70. package/package.json +10 -11
  71. package/src/actions.js +163 -194
  72. package/src/batch/default-processor.js +57 -26
  73. package/src/batch/test/default-processor.js +53 -26
  74. package/src/entities.js +47 -19
  75. package/src/fetch/__experimental-fetch-url-data.js +1 -1
  76. package/src/index.js +7 -10
  77. package/src/locks/actions.js +10 -61
  78. package/src/locks/engine.js +43 -0
  79. package/src/locks/reducer.js +1 -3
  80. package/src/locks/selectors.js +4 -4
  81. package/src/locks/test/engine.js +135 -0
  82. package/src/locks/test/reducer.js +1 -1
  83. package/src/locks/test/selectors.js +105 -124
  84. package/src/queried-data/get-query-parts.js +11 -6
  85. package/src/queried-data/selectors.js +2 -9
  86. package/src/queried-data/test/get-query-parts.js +1 -1
  87. package/src/queried-data/test/selectors.js +1 -0
  88. package/src/reducer.js +14 -19
  89. package/src/resolvers.js +132 -120
  90. package/src/selectors.js +156 -44
  91. package/src/test/actions.js +330 -170
  92. package/src/test/entities.js +40 -26
  93. package/src/test/resolvers.js +270 -223
  94. package/src/test/selectors.js +127 -1
  95. package/src/utils/forward-resolver.js +14 -0
  96. package/src/utils/index.js +2 -1
  97. package/src/utils/is-raw-attribute.js +11 -0
  98. package/src/utils/test/is-raw-attribute.js +22 -0
  99. package/build/controls.js +0 -44
  100. package/build/controls.js.map +0 -1
  101. package/build/locks/index.js +0 -47
  102. package/build/locks/index.js.map +0 -1
  103. package/build/utils/if-not-resolved.js +0 -46
  104. package/build/utils/if-not-resolved.js.map +0 -1
  105. package/build-module/controls.js +0 -31
  106. package/build-module/controls.js.map +0 -1
  107. package/build-module/locks/index.js +0 -4
  108. package/build-module/locks/index.js.map +0 -1
  109. package/build-module/utils/if-not-resolved.js +0 -36
  110. package/build-module/utils/if-not-resolved.js.map +0 -1
  111. package/src/controls.js +0 -31
  112. package/src/locks/index.js +0 -3
  113. package/src/locks/test/actions.js +0 -307
  114. package/src/test/integration.js +0 -264
  115. package/src/utils/if-not-resolved.js +0 -40
  116. 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
- };
@@ -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
- } );