@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,7 +1,9 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { controls } from '@wordpress/data';
4
+ import apiFetch from '@wordpress/api-fetch';
5
+
6
+ jest.mock( '@wordpress/api-fetch' );
5
7
 
6
8
  /**
7
9
  * Internal dependencies
@@ -9,27 +11,14 @@ import { controls } from '@wordpress/data';
9
11
  import {
10
12
  editEntityRecord,
11
13
  saveEntityRecord,
14
+ saveEditedEntityRecord,
12
15
  deleteEntityRecord,
13
- receiveEntityRecords,
14
16
  receiveUserPermission,
15
17
  receiveAutosaves,
16
18
  receiveCurrentUser,
17
19
  __experimentalBatch,
18
20
  } from '../actions';
19
21
 
20
- jest.mock( '../locks/actions', () => ( {
21
- __unstableAcquireStoreLock: jest.fn( () => [
22
- {
23
- type: 'MOCKED_ACQUIRE_LOCK',
24
- },
25
- ] ),
26
- __unstableReleaseStoreLock: jest.fn( () => [
27
- {
28
- type: 'MOCKED_RELEASE_LOCK',
29
- },
30
- ] ),
31
- } ) );
32
-
33
22
  jest.mock( '../batch', () => {
34
23
  const { createBatch } = jest.requireActual( '../batch' );
35
24
  return {
@@ -40,120 +29,249 @@ jest.mock( '../batch', () => {
40
29
  } );
41
30
 
42
31
  describe( 'editEntityRecord', () => {
43
- it( 'throws when the edited entity does not have a loaded config.', () => {
32
+ it( 'throws when the edited entity does not have a loaded config.', async () => {
44
33
  const entity = { kind: 'someKind', name: 'someName', id: 'someId' };
45
- const fulfillment = editEntityRecord(
46
- entity.kind,
47
- entity.name,
48
- entity.id,
49
- {}
50
- );
51
- expect( fulfillment.next().value ).toEqual(
52
- controls.select( 'core', 'getEntity', entity.kind, entity.name )
53
- );
54
-
55
- // Don't pass back an entity config.
56
- expect( fulfillment.next.bind( fulfillment ) ).toThrow(
34
+ const select = {
35
+ getEntity: jest.fn(),
36
+ };
37
+ const fulfillment = () =>
38
+ editEntityRecord(
39
+ entity.kind,
40
+ entity.name,
41
+ entity.id,
42
+ {}
43
+ )( { select } );
44
+ expect( fulfillment ).toThrow(
57
45
  `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.`
58
46
  );
47
+ expect( select.getEntity ).toHaveBeenCalledTimes( 1 );
59
48
  } );
60
49
  } );
61
50
 
62
51
  describe( 'deleteEntityRecord', () => {
52
+ beforeEach( async () => {
53
+ apiFetch.mockReset();
54
+ jest.useFakeTimers();
55
+ } );
56
+
63
57
  it( 'triggers a DELETE request for an existing record', async () => {
64
- const post = 10;
58
+ const deletedRecord = { title: 'new post', id: 10 };
65
59
  const entities = [
66
60
  { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' },
67
61
  ];
68
- const fulfillment = deleteEntityRecord( 'postType', 'post', post );
69
62
 
70
- // Trigger generator
71
- fulfillment.next();
63
+ const dispatch = Object.assign( jest.fn(), {
64
+ receiveEntityRecords: jest.fn(),
65
+ __unstableAcquireStoreLock: jest.fn(),
66
+ __unstableReleaseStoreLock: jest.fn(),
67
+ } );
68
+ // Provide entities
69
+ dispatch.mockReturnValueOnce( entities );
72
70
 
73
- // Acquire lock
74
- expect( fulfillment.next( entities ).value.type ).toBe(
75
- 'MOCKED_ACQUIRE_LOCK'
76
- );
71
+ // Provide response
72
+ apiFetch.mockImplementation( () => deletedRecord );
77
73
 
78
- // Start
79
- expect( fulfillment.next().value.type ).toEqual(
80
- 'DELETE_ENTITY_RECORD_START'
81
- );
74
+ const result = await deleteEntityRecord(
75
+ 'postType',
76
+ 'post',
77
+ deletedRecord.id
78
+ )( { dispatch } );
82
79
 
83
- // delete api call
84
- const { value: apiFetchAction } = fulfillment.next();
85
- expect( apiFetchAction.request ).toEqual( {
80
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
81
+ expect( apiFetch ).toHaveBeenCalledWith( {
86
82
  path: '/wp/v2/posts/10',
87
83
  method: 'DELETE',
88
84
  } );
89
85
 
90
- expect( fulfillment.next().value.type ).toBe( 'REMOVE_ITEMS' );
91
-
92
- expect( fulfillment.next().value.type ).toBe(
93
- 'DELETE_ENTITY_RECORD_FINISH'
86
+ expect( dispatch ).toHaveBeenCalledTimes( 4 );
87
+ expect( dispatch ).toHaveBeenCalledWith( {
88
+ type: 'DELETE_ENTITY_RECORD_START',
89
+ kind: 'postType',
90
+ name: 'post',
91
+ recordId: 10,
92
+ } );
93
+ expect( dispatch ).toHaveBeenCalledWith( {
94
+ type: 'DELETE_ENTITY_RECORD_FINISH',
95
+ kind: 'postType',
96
+ name: 'post',
97
+ recordId: 10,
98
+ error: undefined,
99
+ } );
100
+ expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes(
101
+ 1
102
+ );
103
+ expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes(
104
+ 1
94
105
  );
95
106
 
96
- // Release lock
97
- expect( fulfillment.next().value.type ).toEqual(
98
- 'MOCKED_RELEASE_LOCK'
107
+ expect( result ).toBe( deletedRecord );
108
+ } );
109
+ } );
110
+
111
+ describe( 'saveEditedEntityRecord', () => {
112
+ beforeEach( async () => {
113
+ apiFetch.mockReset();
114
+ jest.useFakeTimers();
115
+ } );
116
+
117
+ it( 'Uses "id" as a key when no entity key is provided', async () => {
118
+ const area = { id: 1, menu: 0 };
119
+ const entities = [
120
+ {
121
+ kind: 'root',
122
+ name: 'navigationArea',
123
+ baseURL: '/__experimental/block-navigation-areas',
124
+ },
125
+ ];
126
+ const select = {
127
+ getEntityRecordNonTransientEdits: () => [],
128
+ hasEditsForEntityRecord: () => true,
129
+ };
130
+
131
+ const dispatch = Object.assign( jest.fn(), {
132
+ saveEntityRecord: jest.fn(),
133
+ } );
134
+ // Provide entities
135
+ dispatch.mockReturnValueOnce( entities );
136
+
137
+ // Provide response
138
+ const updatedRecord = { ...area, menu: 10 };
139
+ apiFetch.mockImplementation( () => {
140
+ return updatedRecord;
141
+ } );
142
+
143
+ await saveEditedEntityRecord(
144
+ 'root',
145
+ 'navigationArea',
146
+ 1
147
+ )( { dispatch, select } );
148
+
149
+ expect( dispatch.saveEntityRecord ).toHaveBeenCalledWith(
150
+ 'root',
151
+ 'navigationArea',
152
+ { id: 1 },
153
+ undefined
99
154
  );
155
+ } );
156
+
157
+ it( 'Uses the entity key when provided', async () => {
158
+ const area = { area: 'primary', menu: 0 };
159
+ const entities = [
160
+ {
161
+ kind: 'root',
162
+ name: 'navigationArea',
163
+ baseURL: '/__experimental/block-navigation-areas',
164
+ key: 'area',
165
+ },
166
+ ];
167
+ const select = {
168
+ getEntityRecordNonTransientEdits: () => [],
169
+ hasEditsForEntityRecord: () => true,
170
+ };
100
171
 
101
- expect( fulfillment.next() ).toMatchObject( {
102
- done: true,
103
- value: undefined,
172
+ const dispatch = Object.assign( jest.fn(), {
173
+ saveEntityRecord: jest.fn(),
104
174
  } );
175
+ // Provide entities
176
+ dispatch.mockReturnValueOnce( entities );
177
+
178
+ // Provide response
179
+ const updatedRecord = { ...area, menu: 10 };
180
+ apiFetch.mockImplementation( () => {
181
+ return updatedRecord;
182
+ } );
183
+
184
+ await saveEditedEntityRecord(
185
+ 'root',
186
+ 'navigationArea',
187
+ 'primary'
188
+ )( { dispatch, select } );
189
+
190
+ expect( dispatch.saveEntityRecord ).toHaveBeenCalledWith(
191
+ 'root',
192
+ 'navigationArea',
193
+ { area: 'primary' },
194
+ undefined
195
+ );
105
196
  } );
106
197
  } );
107
198
 
108
199
  describe( 'saveEntityRecord', () => {
200
+ beforeEach( async () => {
201
+ apiFetch.mockReset();
202
+ jest.useFakeTimers();
203
+ } );
204
+
109
205
  it( 'triggers a POST request for a new record', async () => {
110
206
  const post = { title: 'new post' };
111
207
  const entities = [
112
208
  { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' },
113
209
  ];
114
- const fulfillment = saveEntityRecord( 'postType', 'post', post );
115
- // Trigger generator
116
- fulfillment.next();
210
+ const select = {
211
+ getRawEntityRecord: () => post,
212
+ };
117
213
 
118
- // Provide entities and acquire lock
119
- expect( fulfillment.next( entities ).value.type ).toBe(
120
- 'MOCKED_ACQUIRE_LOCK'
121
- );
214
+ const dispatch = Object.assign( jest.fn(), {
215
+ receiveEntityRecords: jest.fn(),
216
+ __unstableAcquireStoreLock: jest.fn(),
217
+ __unstableReleaseStoreLock: jest.fn(),
218
+ } );
219
+ // Provide entities
220
+ dispatch.mockReturnValueOnce( entities );
122
221
 
123
- // Trigger apiFetch
124
- expect( fulfillment.next().value.type ).toEqual(
125
- 'SAVE_ENTITY_RECORD_START'
126
- );
222
+ // Provide response
223
+ const updatedRecord = { ...post, id: 10 };
224
+ apiFetch.mockImplementation( () => {
225
+ return updatedRecord;
226
+ } );
227
+
228
+ const result = await saveEntityRecord(
229
+ 'postType',
230
+ 'post',
231
+ post
232
+ )( { select, dispatch } );
127
233
 
128
- expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
129
- const { value: apiFetchAction } = fulfillment.next( {} );
130
- expect( apiFetchAction.request ).toEqual( {
234
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
235
+ expect( apiFetch ).toHaveBeenCalledWith( {
131
236
  path: '/wp/v2/posts',
132
237
  method: 'POST',
133
238
  data: post,
134
239
  } );
135
- // Provide response and trigger action
136
- const updatedRecord = { ...post, id: 10 };
137
- const { value: received } = fulfillment.next( updatedRecord );
138
- expect( received ).toEqual(
139
- receiveEntityRecords(
140
- 'postType',
141
- 'post',
142
- updatedRecord,
143
- undefined,
144
- true,
145
- { title: 'new post' }
146
- )
240
+
241
+ expect( dispatch ).toHaveBeenCalledTimes( 3 );
242
+ expect( dispatch ).toHaveBeenCalledWith( {
243
+ type: 'SAVE_ENTITY_RECORD_START',
244
+ kind: 'postType',
245
+ name: 'post',
246
+ recordId: undefined,
247
+ isAutosave: false,
248
+ } );
249
+ expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes(
250
+ 1
147
251
  );
148
- expect( fulfillment.next().value.type ).toBe(
149
- 'SAVE_ENTITY_RECORD_FINISH'
252
+ expect( dispatch ).toHaveBeenCalledWith( {
253
+ type: 'SAVE_ENTITY_RECORD_FINISH',
254
+ kind: 'postType',
255
+ name: 'post',
256
+ recordId: undefined,
257
+ error: undefined,
258
+ isAutosave: false,
259
+ } );
260
+ expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes(
261
+ 1
150
262
  );
151
- // Release lock
152
- expect( fulfillment.next().value.type ).toEqual(
153
- 'MOCKED_RELEASE_LOCK'
263
+
264
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 );
265
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith(
266
+ 'postType',
267
+ 'post',
268
+ updatedRecord,
269
+ undefined,
270
+ true,
271
+ post
154
272
  );
155
273
 
156
- expect( fulfillment.next().value ).toBe( updatedRecord );
274
+ expect( result ).toBe( updatedRecord );
157
275
  } );
158
276
 
159
277
  it( 'triggers a PUT request for an existing record', async () => {
@@ -161,41 +279,71 @@ describe( 'saveEntityRecord', () => {
161
279
  const entities = [
162
280
  { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' },
163
281
  ];
164
- const fulfillment = saveEntityRecord( 'postType', 'post', post );
165
- // Trigger generator
166
- fulfillment.next();
282
+ const select = {
283
+ getRawEntityRecord: () => post,
284
+ };
167
285
 
168
- // Provide entities and acquire lock
169
- expect( fulfillment.next( entities ).value.type ).toBe(
170
- 'MOCKED_ACQUIRE_LOCK'
171
- );
286
+ const dispatch = Object.assign( jest.fn(), {
287
+ receiveEntityRecords: jest.fn(),
288
+ __unstableAcquireStoreLock: jest.fn(),
289
+ __unstableReleaseStoreLock: jest.fn(),
290
+ } );
291
+ // Provide entities
292
+ dispatch.mockReturnValueOnce( entities );
172
293
 
173
- // Trigger apiFetch
174
- expect( fulfillment.next().value.type ).toEqual(
175
- 'SAVE_ENTITY_RECORD_START'
176
- );
177
- expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
178
- const { value: apiFetchAction } = fulfillment.next( {} );
179
- expect( apiFetchAction.request ).toEqual( {
294
+ // Provide response
295
+ const updatedRecord = { ...post, id: 10 };
296
+ apiFetch.mockImplementation( () => {
297
+ return updatedRecord;
298
+ } );
299
+
300
+ const result = await saveEntityRecord(
301
+ 'postType',
302
+ 'post',
303
+ post
304
+ )( { select, dispatch } );
305
+
306
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
307
+ expect( apiFetch ).toHaveBeenCalledWith( {
180
308
  path: '/wp/v2/posts/10',
181
309
  method: 'PUT',
182
310
  data: post,
183
311
  } );
184
- // Provide response and trigger action
185
- const { value: received } = fulfillment.next( post );
186
- expect( received ).toEqual(
187
- receiveEntityRecords( 'postType', 'post', post, undefined, true, {
188
- title: 'new post',
189
- id: 10,
190
- } )
312
+
313
+ expect( dispatch ).toHaveBeenCalledTimes( 3 );
314
+ expect( dispatch ).toHaveBeenCalledWith( {
315
+ type: 'SAVE_ENTITY_RECORD_START',
316
+ kind: 'postType',
317
+ name: 'post',
318
+ recordId: 10,
319
+ isAutosave: false,
320
+ } );
321
+ expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes(
322
+ 1
191
323
  );
192
- expect( fulfillment.next().value.type ).toBe(
193
- 'SAVE_ENTITY_RECORD_FINISH'
324
+ expect( dispatch ).toHaveBeenCalledWith( {
325
+ type: 'SAVE_ENTITY_RECORD_FINISH',
326
+ kind: 'postType',
327
+ name: 'post',
328
+ recordId: 10,
329
+ error: undefined,
330
+ isAutosave: false,
331
+ } );
332
+ expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes(
333
+ 1
194
334
  );
195
- // Release lock
196
- expect( fulfillment.next().value.type ).toEqual(
197
- 'MOCKED_RELEASE_LOCK'
335
+
336
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 );
337
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith(
338
+ 'postType',
339
+ 'post',
340
+ updatedRecord,
341
+ undefined,
342
+ true,
343
+ post
198
344
  );
345
+
346
+ expect( result ).toBe( updatedRecord );
199
347
  } );
200
348
 
201
349
  it( 'triggers a PUT request for an existing record with a custom key', async () => {
@@ -208,45 +356,68 @@ describe( 'saveEntityRecord', () => {
208
356
  key: 'slug',
209
357
  },
210
358
  ];
211
- const fulfillment = saveEntityRecord( 'root', 'postType', postType );
212
- // Trigger generator
213
- fulfillment.next();
359
+ const select = {
360
+ getRawEntityRecord: () => ( {} ),
361
+ };
214
362
 
215
- // Provide entities and acquire lock
216
- expect( fulfillment.next( entities ).value.type ).toBe(
217
- 'MOCKED_ACQUIRE_LOCK'
218
- );
363
+ const dispatch = Object.assign( jest.fn(), {
364
+ receiveEntityRecords: jest.fn(),
365
+ __unstableAcquireStoreLock: jest.fn(),
366
+ __unstableReleaseStoreLock: jest.fn(),
367
+ } );
368
+ // Provide entities
369
+ dispatch.mockReturnValueOnce( entities );
219
370
 
220
- // Trigger apiFetch
221
- expect( fulfillment.next().value.type ).toEqual(
222
- 'SAVE_ENTITY_RECORD_START'
223
- );
224
- expect( fulfillment.next().value.type ).toBe( '@@data/SELECT' );
225
- const { value: apiFetchAction } = fulfillment.next( {} );
226
- expect( apiFetchAction.request ).toEqual( {
371
+ // Provide response
372
+ apiFetch.mockImplementation( () => postType );
373
+
374
+ const result = await saveEntityRecord(
375
+ 'root',
376
+ 'postType',
377
+ postType
378
+ )( { select, dispatch } );
379
+
380
+ expect( apiFetch ).toHaveBeenCalledTimes( 1 );
381
+ expect( apiFetch ).toHaveBeenCalledWith( {
227
382
  path: '/wp/v2/types/page',
228
383
  method: 'PUT',
229
384
  data: postType,
230
385
  } );
231
- // Provide response and trigger action
232
- const { value: received } = fulfillment.next( postType );
233
- expect( received ).toEqual(
234
- receiveEntityRecords(
235
- 'root',
236
- 'postType',
237
- postType,
238
- undefined,
239
- true,
240
- { slug: 'page', title: 'Pages' }
241
- )
386
+
387
+ expect( dispatch ).toHaveBeenCalledTimes( 3 );
388
+ expect( dispatch ).toHaveBeenCalledWith( {
389
+ type: 'SAVE_ENTITY_RECORD_START',
390
+ kind: 'root',
391
+ name: 'postType',
392
+ recordId: 'page',
393
+ isAutosave: false,
394
+ } );
395
+ expect( dispatch.__unstableAcquireStoreLock ).toHaveBeenCalledTimes(
396
+ 1
242
397
  );
243
- expect( fulfillment.next().value.type ).toBe(
244
- 'SAVE_ENTITY_RECORD_FINISH'
398
+ expect( dispatch ).toHaveBeenCalledWith( {
399
+ type: 'SAVE_ENTITY_RECORD_FINISH',
400
+ kind: 'root',
401
+ name: 'postType',
402
+ recordId: 'page',
403
+ error: undefined,
404
+ isAutosave: false,
405
+ } );
406
+ expect( dispatch.__unstableReleaseStoreLock ).toHaveBeenCalledTimes(
407
+ 1
245
408
  );
246
- // Release lock
247
- expect( fulfillment.next().value.type ).toEqual(
248
- 'MOCKED_RELEASE_LOCK'
409
+
410
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledTimes( 1 );
411
+ expect( dispatch.receiveEntityRecords ).toHaveBeenCalledWith(
412
+ 'root',
413
+ 'postType',
414
+ postType,
415
+ undefined,
416
+ true,
417
+ { slug: 'page', title: 'Pages' }
249
418
  );
419
+
420
+ expect( result ).toBe( postType );
250
421
  } );
251
422
  } );
252
423
 
@@ -305,21 +476,7 @@ describe( 'receiveCurrentUser', () => {
305
476
 
306
477
  describe( '__experimentalBatch', () => {
307
478
  it( 'batches multiple actions together', async () => {
308
- const generator = __experimentalBatch(
309
- [
310
- ( { saveEntityRecord: _saveEntityRecord } ) =>
311
- _saveEntityRecord( 'root', 'widget', {} ),
312
- ( { saveEditedEntityRecord: _saveEditedEntityRecord } ) =>
313
- _saveEditedEntityRecord( 'root', 'widget', 123 ),
314
- ( { deleteEntityRecord: _deleteEntityRecord } ) =>
315
- _deleteEntityRecord( 'root', 'widget', 123, {} ),
316
- ],
317
- { __unstableProcessor: ( inputs ) => Promise.resolve( inputs ) }
318
- );
319
- // Run generator up to `yield getDispatch()`.
320
- const { value: getDispatchControl } = generator.next();
321
- expect( getDispatchControl ).toEqual( { type: 'GET_DISPATCH' } );
322
- const actions = {
479
+ const dispatch = {
323
480
  saveEntityRecord: jest.fn(
324
481
  ( kind, name, record, { __unstableFetch } ) => {
325
482
  __unstableFetch( {} );
@@ -339,36 +496,39 @@ describe( '__experimentalBatch', () => {
339
496
  }
340
497
  ),
341
498
  };
342
- const dispatch = () => actions;
343
- // Run generator up to `yield __unstableAwaitPromise( ... )`.
344
- const { value: awaitPromiseControl } = generator.next( dispatch );
345
- expect( actions.saveEntityRecord ).toHaveBeenCalledWith(
499
+
500
+ const results = await __experimentalBatch(
501
+ [
502
+ ( { saveEntityRecord: _saveEntityRecord } ) =>
503
+ _saveEntityRecord( 'root', 'widget', {} ),
504
+ ( { saveEditedEntityRecord: _saveEditedEntityRecord } ) =>
505
+ _saveEditedEntityRecord( 'root', 'widget', 123 ),
506
+ ( { deleteEntityRecord: _deleteEntityRecord } ) =>
507
+ _deleteEntityRecord( 'root', 'widget', 123, {} ),
508
+ ],
509
+ { __unstableProcessor: ( inputs ) => Promise.resolve( inputs ) }
510
+ )( { dispatch } );
511
+
512
+ expect( dispatch.saveEntityRecord ).toHaveBeenCalledWith(
346
513
  'root',
347
514
  'widget',
348
515
  {},
349
516
  { __unstableFetch: expect.any( Function ) }
350
517
  );
351
- expect( actions.saveEditedEntityRecord ).toHaveBeenCalledWith(
518
+ expect( dispatch.saveEditedEntityRecord ).toHaveBeenCalledWith(
352
519
  'root',
353
520
  'widget',
354
521
  123,
355
522
  { __unstableFetch: expect.any( Function ) }
356
523
  );
357
- expect( actions.deleteEntityRecord ).toHaveBeenCalledWith(
524
+ expect( dispatch.deleteEntityRecord ).toHaveBeenCalledWith(
358
525
  'root',
359
526
  'widget',
360
527
  123,
361
528
  {},
362
529
  { __unstableFetch: expect.any( Function ) }
363
530
  );
364
- expect( awaitPromiseControl ).toEqual( {
365
- type: 'AWAIT_PROMISE',
366
- promise: expect.any( Promise ),
367
- } );
368
- // Run generator to the end.
369
- const { value: results } = generator.next(
370
- await awaitPromiseControl.promise
371
- );
531
+
372
532
  expect( results ).toEqual( [
373
533
  { id: 123, created: true },
374
534
  { id: 123, updated: true },