@wordpress/core-data 7.48.0 → 7.48.2-next.v.202606191442.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/build/actions.cjs +1 -7
- package/build/actions.cjs.map +3 -3
- package/build/awareness/block-lookup.cjs +14 -26
- package/build/awareness/block-lookup.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +4 -3
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/entities.cjs +6 -3
- package/build/entities.cjs.map +2 -2
- package/build/entity-types/helpers.cjs.map +1 -1
- package/build/hooks/use-entity-record.cjs +21 -19
- package/build/hooks/use-entity-record.cjs.map +3 -3
- package/build/hooks/use-entity-records.cjs +22 -20
- package/build/hooks/use-entity-records.cjs.map +3 -3
- package/build/hooks/use-post-editor-awareness-state.cjs +8 -2
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/hooks/use-query-select.cjs +2 -20
- package/build/hooks/use-query-select.cjs.map +2 -2
- package/build/hooks/utils.cjs +53 -0
- package/build/hooks/utils.cjs.map +7 -0
- package/build/private-actions.cjs +8 -0
- package/build/private-actions.cjs.map +2 -2
- package/build/private-selectors.cjs.map +2 -2
- package/build/reducer.cjs +23 -7
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +13 -8
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +7 -0
- package/build/selectors.cjs.map +2 -2
- package/build/types.cjs.map +1 -1
- package/build/utils/clear-unchanged-edits.cjs +51 -0
- package/build/utils/clear-unchanged-edits.cjs.map +7 -0
- package/build/utils/crdt-blocks.cjs +12 -2
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-user-selections.cjs.map +1 -1
- package/build/utils/crdt-utils.cjs.map +1 -1
- package/build/utils/crdt.cjs +2 -1
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +6 -0
- package/build/utils/index.cjs.map +2 -2
- package/build/utils/save-crdt-doc.cjs +75 -0
- package/build/utils/save-crdt-doc.cjs.map +7 -0
- package/build/utils/set-nested-value.cjs.map +1 -1
- package/build-module/actions.mjs +2 -8
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/block-lookup.mjs +13 -26
- package/build-module/awareness/block-lookup.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +4 -3
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +6 -3
- package/build-module/entities.mjs.map +2 -2
- package/build-module/hooks/use-entity-record.mjs +21 -19
- package/build-module/hooks/use-entity-record.mjs.map +2 -2
- package/build-module/hooks/use-entity-records.mjs +20 -18
- package/build-module/hooks/use-entity-records.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +9 -3
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/hooks/use-query-select.mjs +2 -20
- package/build-module/hooks/use-query-select.mjs.map +2 -2
- package/build-module/hooks/utils.mjs +28 -0
- package/build-module/hooks/utils.mjs.map +7 -0
- package/build-module/private-actions.mjs +7 -0
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/reducer.mjs +23 -8
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +15 -9
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +7 -0
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/utils/clear-unchanged-edits.mjs +20 -0
- package/build-module/utils/clear-unchanged-edits.mjs.map +7 -0
- package/build-module/utils/crdt-blocks.mjs +12 -2
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-user-selections.mjs.map +1 -1
- package/build-module/utils/crdt-utils.mjs.map +1 -1
- package/build-module/utils/crdt.mjs +2 -1
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +24 -20
- package/build-module/utils/index.mjs.map +2 -2
- package/build-module/utils/save-crdt-doc.mjs +40 -0
- package/build-module/utils/save-crdt-doc.mjs.map +7 -0
- package/build-module/utils/set-nested-value.mjs.map +1 -1
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/block-lookup.d.ts +27 -7
- package/build-types/awareness/block-lookup.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +3 -1
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/hooks/use-entity-record.d.ts +4 -0
- package/build-types/hooks/use-entity-record.d.ts.map +1 -1
- package/build-types/hooks/use-entity-records.d.ts +5 -1
- package/build-types/hooks/use-entity-records.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/hooks/utils.d.ts +22 -0
- package/build-types/hooks/utils.d.ts.map +1 -0
- package/build-types/index.d.ts +8 -8
- package/build-types/private-actions.d.ts +15 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +0 -12
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/reducer.d.ts +15 -0
- package/build-types/reducer.d.ts.map +1 -1
- package/build-types/resolvers.d.ts.map +1 -1
- package/build-types/selectors.d.ts +12 -8
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/utils/clear-unchanged-edits.d.ts +12 -0
- package/build-types/utils/clear-unchanged-edits.d.ts.map +1 -0
- package/build-types/utils/crdt-blocks.d.ts +5 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/index.d.ts +2 -0
- package/build-types/utils/index.d.ts.map +1 -1
- package/build-types/utils/save-crdt-doc.d.ts +8 -0
- package/build-types/utils/save-crdt-doc.d.ts.map +1 -0
- package/package.json +27 -20
- package/src/actions.js +2 -10
- package/src/awareness/block-lookup.ts +21 -62
- package/src/awareness/post-editor-awareness.ts +8 -3
- package/src/awareness/test/block-lookup.ts +98 -94
- package/src/awareness/test/post-editor-awareness.ts +177 -180
- package/src/entities.js +14 -3
- package/src/hooks/test/use-entity-record.js +5 -1
- package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
- package/src/hooks/use-entity-record.ts +26 -19
- package/src/hooks/use-entity-records.ts +26 -18
- package/src/hooks/use-post-editor-awareness-state.ts +20 -7
- package/src/hooks/use-query-select.ts +2 -23
- package/src/hooks/utils.ts +40 -0
- package/src/private-actions.js +18 -0
- package/src/private-selectors.ts +0 -12
- package/src/reducer.js +30 -9
- package/src/resolvers.js +20 -13
- package/src/selectors.ts +11 -0
- package/src/test/entities.js +51 -0
- package/src/test/private-selectors.js +66 -0
- package/src/test/reducer.js +44 -0
- package/src/test/resolvers.js +121 -113
- package/src/test/selectors.js +48 -0
- package/src/utils/clear-unchanged-edits.ts +34 -0
- package/src/utils/crdt-blocks.ts +27 -22
- package/src/utils/crdt.ts +2 -1
- package/src/utils/index.js +2 -0
- package/src/utils/save-crdt-doc.js +64 -0
- package/src/utils/test/clear-unchanged-edits.js +42 -0
- package/src/utils/test/crdt-blocks.ts +57 -2
- package/src/utils/test/rtc-rich-text-cursor-scope.test.js +2 -2
- package/src/utils/test/save-crdt-doc.js +185 -0
package/src/test/reducer.js
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
userPermissions,
|
|
13
13
|
autosaves,
|
|
14
14
|
currentUser,
|
|
15
|
+
syncUndoManagerState,
|
|
16
|
+
undoManager,
|
|
15
17
|
} from '../reducer';
|
|
16
18
|
|
|
17
19
|
describe( 'entities', () => {
|
|
@@ -531,3 +533,45 @@ describe( 'currentUser', () => {
|
|
|
531
533
|
expect( state ).toEqual( currentUserData );
|
|
532
534
|
} );
|
|
533
535
|
} );
|
|
536
|
+
|
|
537
|
+
describe( 'undoManager', () => {
|
|
538
|
+
it( 'returns the same reference for unrelated actions', () => {
|
|
539
|
+
const originalState = undoManager( undefined, {} );
|
|
540
|
+
const state = undoManager( originalState, {
|
|
541
|
+
type: 'UNRELATED',
|
|
542
|
+
} );
|
|
543
|
+
|
|
544
|
+
expect( state ).toBe( originalState );
|
|
545
|
+
} );
|
|
546
|
+
} );
|
|
547
|
+
|
|
548
|
+
describe( 'syncUndoManagerState', () => {
|
|
549
|
+
it( 'stores sync undo manager availability', () => {
|
|
550
|
+
const state = syncUndoManagerState( undefined, {
|
|
551
|
+
type: 'SYNC_UNDO_MANAGER_CHANGE',
|
|
552
|
+
hasRedo: false,
|
|
553
|
+
hasUndo: true,
|
|
554
|
+
} );
|
|
555
|
+
|
|
556
|
+
expect( state ).toEqual( {
|
|
557
|
+
hasRedo: false,
|
|
558
|
+
hasUndo: true,
|
|
559
|
+
} );
|
|
560
|
+
} );
|
|
561
|
+
|
|
562
|
+
it( 'updates sync undo manager availability', () => {
|
|
563
|
+
const state = syncUndoManagerState(
|
|
564
|
+
{ hasRedo: false, hasUndo: true },
|
|
565
|
+
{
|
|
566
|
+
type: 'SYNC_UNDO_MANAGER_CHANGE',
|
|
567
|
+
hasRedo: true,
|
|
568
|
+
hasUndo: false,
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
expect( state ).toEqual( {
|
|
573
|
+
hasRedo: true,
|
|
574
|
+
hasUndo: false,
|
|
575
|
+
} );
|
|
576
|
+
} );
|
|
577
|
+
} );
|
package/src/test/resolvers.js
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
getAutosaves,
|
|
26
26
|
getCurrentUser,
|
|
27
27
|
} from '../resolvers';
|
|
28
|
-
import { saveEntityRecord } from '../actions';
|
|
29
28
|
import { RECEIVE_INTERMEDIATE_RESULTS } from '../utils';
|
|
30
29
|
|
|
31
30
|
describe( 'getEntityRecord', () => {
|
|
@@ -50,6 +49,7 @@ describe( 'getEntityRecord', () => {
|
|
|
50
49
|
receiveEntityRecords: jest.fn(),
|
|
51
50
|
__unstableAcquireStoreLock: jest.fn(),
|
|
52
51
|
__unstableReleaseStoreLock: jest.fn(),
|
|
52
|
+
__unstableNotifySyncUndoManagerChange: jest.fn(),
|
|
53
53
|
receiveUserPermissions: jest.fn(),
|
|
54
54
|
finishResolutions: jest.fn(),
|
|
55
55
|
} );
|
|
@@ -57,6 +57,7 @@ describe( 'getEntityRecord', () => {
|
|
|
57
57
|
|
|
58
58
|
syncManager = {
|
|
59
59
|
load: jest.fn(),
|
|
60
|
+
update: jest.fn(),
|
|
60
61
|
};
|
|
61
62
|
getSyncManager.mockImplementation( () => syncManager );
|
|
62
63
|
} );
|
|
@@ -173,6 +174,7 @@ describe( 'getEntityRecord', () => {
|
|
|
173
174
|
addUndoMeta: expect.any( Function ),
|
|
174
175
|
editRecord: expect.any( Function ),
|
|
175
176
|
getEditedRecord: expect.any( Function ),
|
|
177
|
+
onUndoStackChange: expect.any( Function ),
|
|
176
178
|
onStatusChange: expect.any( Function ),
|
|
177
179
|
persistCRDTDoc: expect.any( Function ),
|
|
178
180
|
refetchRecord: expect.any( Function ),
|
|
@@ -181,7 +183,48 @@ describe( 'getEntityRecord', () => {
|
|
|
181
183
|
);
|
|
182
184
|
} );
|
|
183
185
|
|
|
184
|
-
it( '
|
|
186
|
+
it( 'notifies core-data when the sync undo manager stack changes', async () => {
|
|
187
|
+
const POST_RECORD = { id: 1, title: 'Test Post' };
|
|
188
|
+
const POST_RESPONSE = {
|
|
189
|
+
json: () => Promise.resolve( POST_RECORD ),
|
|
190
|
+
};
|
|
191
|
+
const ENTITIES_WITH_SYNC = [
|
|
192
|
+
{
|
|
193
|
+
name: 'post',
|
|
194
|
+
kind: 'postType',
|
|
195
|
+
baseURL: '/wp/v2/posts',
|
|
196
|
+
baseURLParams: { context: 'edit' },
|
|
197
|
+
syncConfig: {},
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
const resolveSelectWithSync = {
|
|
202
|
+
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
203
|
+
getEditedEntityRecord: jest.fn(),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
triggerFetch.mockImplementation( () => POST_RESPONSE );
|
|
207
|
+
|
|
208
|
+
await getEntityRecord(
|
|
209
|
+
'postType',
|
|
210
|
+
'post',
|
|
211
|
+
1
|
|
212
|
+
)( {
|
|
213
|
+
dispatch,
|
|
214
|
+
registry,
|
|
215
|
+
resolveSelect: resolveSelectWithSync,
|
|
216
|
+
} );
|
|
217
|
+
|
|
218
|
+
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
219
|
+
|
|
220
|
+
handlers.onUndoStackChange( { hasRedo: false, hasUndo: true } );
|
|
221
|
+
|
|
222
|
+
expect(
|
|
223
|
+
dispatch.__unstableNotifySyncUndoManagerChange
|
|
224
|
+
).toHaveBeenCalledWith( { hasRedo: false, hasUndo: true } );
|
|
225
|
+
} );
|
|
226
|
+
|
|
227
|
+
it( 'persistCRDTDoc fetches edited post record and does not save when the entity does not support meta', async () => {
|
|
185
228
|
const ENTITY_RECORD = { id: 1, title: 'Test Record' };
|
|
186
229
|
const EDITED_RECORD = { id: 1, title: 'Edited Record' };
|
|
187
230
|
const ENTITY_RESPONSE = {
|
|
@@ -189,15 +232,16 @@ describe( 'getEntityRecord', () => {
|
|
|
189
232
|
};
|
|
190
233
|
const ENTITIES_WITH_SYNC = [
|
|
191
234
|
{
|
|
192
|
-
name: '
|
|
193
|
-
kind: '
|
|
194
|
-
baseURL: '/wp/v2/
|
|
235
|
+
name: 'post',
|
|
236
|
+
kind: 'postType',
|
|
237
|
+
baseURL: '/wp/v2/posts',
|
|
195
238
|
baseURLParams: { context: 'edit' },
|
|
196
|
-
syncConfig: {},
|
|
239
|
+
syncConfig: { supportsPersistence: true },
|
|
197
240
|
},
|
|
198
241
|
];
|
|
199
242
|
|
|
200
243
|
dispatch.saveEntityRecord = jest.fn();
|
|
244
|
+
syncManager.createPersistedCRDTDoc = jest.fn();
|
|
201
245
|
|
|
202
246
|
const resolveSelectWithSync = {
|
|
203
247
|
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
@@ -209,8 +253,8 @@ describe( 'getEntityRecord', () => {
|
|
|
209
253
|
triggerFetch.mockImplementation( () => ENTITY_RESPONSE );
|
|
210
254
|
|
|
211
255
|
await getEntityRecord(
|
|
212
|
-
'
|
|
213
|
-
'
|
|
256
|
+
'postType',
|
|
257
|
+
'post',
|
|
214
258
|
1
|
|
215
259
|
)( {
|
|
216
260
|
dispatch,
|
|
@@ -222,19 +266,20 @@ describe( 'getEntityRecord', () => {
|
|
|
222
266
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
223
267
|
|
|
224
268
|
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
225
|
-
handlers.persistCRDTDoc();
|
|
226
|
-
await resolveSelectWithSync.getEditedEntityRecord();
|
|
269
|
+
await handlers.persistCRDTDoc();
|
|
227
270
|
|
|
228
271
|
// Should have fetched the full edited entity record.
|
|
229
272
|
expect(
|
|
230
273
|
resolveSelectWithSync.getEditedEntityRecord
|
|
231
|
-
).toHaveBeenCalledWith( '
|
|
274
|
+
).toHaveBeenCalledWith( 'postType', 'post', 1 );
|
|
232
275
|
|
|
233
276
|
// Should not have called saveEntityRecord.
|
|
234
277
|
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
278
|
+
expect( syncManager.createPersistedCRDTDoc ).not.toHaveBeenCalled();
|
|
235
279
|
} );
|
|
236
280
|
|
|
237
|
-
it( 'persistCRDTDoc saves
|
|
281
|
+
it( 'persistCRDTDoc saves post CRDT docs through the sync endpoint', async () => {
|
|
282
|
+
const SERIALIZED_DOC = 'serialized-crdt-doc';
|
|
238
283
|
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
239
284
|
const EDITED_RECORD = {
|
|
240
285
|
id: 1,
|
|
@@ -251,11 +296,14 @@ describe( 'getEntityRecord', () => {
|
|
|
251
296
|
kind: 'postType',
|
|
252
297
|
baseURL: '/wp/v2/posts',
|
|
253
298
|
baseURLParams: { context: 'edit' },
|
|
254
|
-
syncConfig: {},
|
|
299
|
+
syncConfig: { supportsPersistence: true },
|
|
255
300
|
},
|
|
256
301
|
];
|
|
257
302
|
|
|
258
303
|
dispatch.saveEntityRecord = jest.fn();
|
|
304
|
+
syncManager.createPersistedCRDTDoc = jest.fn( () =>
|
|
305
|
+
Promise.resolve( SERIALIZED_DOC )
|
|
306
|
+
);
|
|
259
307
|
|
|
260
308
|
const resolveSelectWithSync = {
|
|
261
309
|
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
@@ -264,7 +312,9 @@ describe( 'getEntityRecord', () => {
|
|
|
264
312
|
),
|
|
265
313
|
};
|
|
266
314
|
|
|
267
|
-
triggerFetch
|
|
315
|
+
triggerFetch
|
|
316
|
+
.mockImplementationOnce( () => POST_RESPONSE )
|
|
317
|
+
.mockImplementationOnce( () => [] );
|
|
268
318
|
|
|
269
319
|
await getEntityRecord(
|
|
270
320
|
'postType',
|
|
@@ -280,25 +330,31 @@ describe( 'getEntityRecord', () => {
|
|
|
280
330
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
281
331
|
|
|
282
332
|
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
283
|
-
handlers.persistCRDTDoc();
|
|
284
|
-
await resolveSelectWithSync.getEditedEntityRecord();
|
|
333
|
+
await handlers.persistCRDTDoc();
|
|
285
334
|
|
|
286
335
|
// Should have fetched the full edited entity record.
|
|
287
336
|
expect(
|
|
288
337
|
resolveSelectWithSync.getEditedEntityRecord
|
|
289
338
|
).toHaveBeenCalledWith( 'postType', 'post', 1 );
|
|
290
339
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
'postType',
|
|
295
|
-
'post',
|
|
296
|
-
{ id: 1 },
|
|
297
|
-
{ __unstableSkipSyncUpdate: true }
|
|
340
|
+
expect( syncManager.createPersistedCRDTDoc ).toHaveBeenCalledWith(
|
|
341
|
+
'postType/post',
|
|
342
|
+
1
|
|
298
343
|
);
|
|
344
|
+
expect( triggerFetch ).toHaveBeenLastCalledWith( {
|
|
345
|
+
path: '/wp-sync/v1/save',
|
|
346
|
+
method: 'POST',
|
|
347
|
+
data: {
|
|
348
|
+
room: 'postType/post:1',
|
|
349
|
+
doc: SERIALIZED_DOC,
|
|
350
|
+
},
|
|
351
|
+
} );
|
|
352
|
+
expect( syncManager.update ).not.toHaveBeenCalled();
|
|
353
|
+
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
299
354
|
} );
|
|
300
355
|
|
|
301
|
-
it( 'persistCRDTDoc
|
|
356
|
+
it( 'persistCRDTDoc persists post CRDT docs even when there are no unsaved edits', async () => {
|
|
357
|
+
const SERIALIZED_DOC = 'serialized-crdt-doc';
|
|
302
358
|
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
303
359
|
const POST_RESPONSE = {
|
|
304
360
|
json: () => Promise.resolve( POST_RECORD ),
|
|
@@ -309,11 +365,14 @@ describe( 'getEntityRecord', () => {
|
|
|
309
365
|
kind: 'postType',
|
|
310
366
|
baseURL: '/wp/v2/posts',
|
|
311
367
|
baseURLParams: { context: 'edit' },
|
|
312
|
-
syncConfig: {},
|
|
368
|
+
syncConfig: { supportsPersistence: true },
|
|
313
369
|
},
|
|
314
370
|
];
|
|
315
371
|
|
|
316
372
|
dispatch.saveEntityRecord = jest.fn();
|
|
373
|
+
syncManager.createPersistedCRDTDoc = jest.fn( () =>
|
|
374
|
+
Promise.resolve( SERIALIZED_DOC )
|
|
375
|
+
);
|
|
317
376
|
|
|
318
377
|
// Return the same record (no edits) from getEditedEntityRecord.
|
|
319
378
|
const resolveSelectWithSync = {
|
|
@@ -323,7 +382,9 @@ describe( 'getEntityRecord', () => {
|
|
|
323
382
|
),
|
|
324
383
|
};
|
|
325
384
|
|
|
326
|
-
triggerFetch
|
|
385
|
+
triggerFetch
|
|
386
|
+
.mockImplementationOnce( () => POST_RESPONSE )
|
|
387
|
+
.mockImplementationOnce( () => [] );
|
|
327
388
|
|
|
328
389
|
await getEntityRecord(
|
|
329
390
|
'postType',
|
|
@@ -338,95 +399,55 @@ describe( 'getEntityRecord', () => {
|
|
|
338
399
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
339
400
|
|
|
340
401
|
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
341
|
-
handlers.persistCRDTDoc();
|
|
342
|
-
await resolveSelectWithSync.getEditedEntityRecord();
|
|
402
|
+
await handlers.persistCRDTDoc();
|
|
343
403
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
'
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
404
|
+
expect( triggerFetch ).toHaveBeenLastCalledWith( {
|
|
405
|
+
path: '/wp-sync/v1/save',
|
|
406
|
+
method: 'POST',
|
|
407
|
+
data: {
|
|
408
|
+
room: 'postType/post:1',
|
|
409
|
+
doc: SERIALIZED_DOC,
|
|
410
|
+
},
|
|
411
|
+
} );
|
|
412
|
+
expect( syncManager.update ).not.toHaveBeenCalled();
|
|
413
|
+
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
351
414
|
} );
|
|
352
415
|
|
|
353
|
-
it( 'persistCRDTDoc does not
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const POST_RECORD = { id: 1, title: INITIAL_TITLE, meta: {} };
|
|
357
|
-
const EDITED_RECORD = { id: 1, title: SYNCED_TITLE, meta: {} };
|
|
358
|
-
const STALE_SAVE_RESPONSE = {
|
|
416
|
+
it( 'persistCRDTDoc does not persist entities whose sync config does not support persistence', async () => {
|
|
417
|
+
const TERM_RECORD = { id: 1, name: 'Category', meta: {} };
|
|
418
|
+
const EDITED_RECORD = {
|
|
359
419
|
id: 1,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const liveSyncState = {
|
|
364
|
-
isSaved: false,
|
|
365
|
-
title: SYNCED_TITLE,
|
|
420
|
+
name: 'Edited Category',
|
|
421
|
+
description: '',
|
|
422
|
+
meta: {},
|
|
366
423
|
};
|
|
367
|
-
const
|
|
368
|
-
json: () => Promise.resolve(
|
|
424
|
+
const TERM_RESPONSE = {
|
|
425
|
+
json: () => Promise.resolve( TERM_RECORD ),
|
|
369
426
|
};
|
|
370
427
|
const ENTITIES_WITH_SYNC = [
|
|
371
428
|
{
|
|
372
|
-
name: '
|
|
373
|
-
kind: '
|
|
374
|
-
baseURL: '/wp/v2/
|
|
429
|
+
name: 'category',
|
|
430
|
+
kind: 'taxonomy',
|
|
431
|
+
baseURL: '/wp/v2/categories',
|
|
375
432
|
baseURLParams: { context: 'edit' },
|
|
376
433
|
syncConfig: {},
|
|
377
|
-
__unstablePrePersist: jest.fn( async () => ( {
|
|
378
|
-
meta: { _crdt_document: 'serialized-crdt-doc' },
|
|
379
|
-
} ) ),
|
|
380
434
|
},
|
|
381
435
|
];
|
|
382
436
|
|
|
383
|
-
const select = {
|
|
384
|
-
getEditedEntityRecord: jest.fn( () => EDITED_RECORD ),
|
|
385
|
-
getRawEntityRecord: jest.fn( () => POST_RECORD ),
|
|
386
|
-
};
|
|
387
437
|
const resolveSelectWithSync = {
|
|
388
438
|
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
389
439
|
getEditedEntityRecord: jest.fn( () =>
|
|
390
440
|
Promise.resolve( EDITED_RECORD )
|
|
391
441
|
),
|
|
392
442
|
};
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
syncManager.update = jest.fn(
|
|
396
|
-
( _objectType, _objectId, changes, _origin, options ) => {
|
|
397
|
-
if (
|
|
398
|
-
Object.prototype.hasOwnProperty.call( changes, 'title' )
|
|
399
|
-
) {
|
|
400
|
-
liveSyncState.title = changes.title;
|
|
401
|
-
}
|
|
402
|
-
if ( options?.isSave ) {
|
|
403
|
-
liveSyncState.isSaved = true;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
);
|
|
407
|
-
dispatch.saveEntityRecord = jest.fn(
|
|
408
|
-
( kind, name, record, options ) => {
|
|
409
|
-
savePromise = saveEntityRecord(
|
|
410
|
-
kind,
|
|
411
|
-
name,
|
|
412
|
-
record,
|
|
413
|
-
options
|
|
414
|
-
)( {
|
|
415
|
-
select,
|
|
416
|
-
dispatch,
|
|
417
|
-
resolveSelect: resolveSelectWithSync,
|
|
418
|
-
} );
|
|
419
|
-
return savePromise;
|
|
420
|
-
}
|
|
421
|
-
);
|
|
443
|
+
dispatch.saveEntityRecord = jest.fn();
|
|
444
|
+
syncManager.createPersistedCRDTDoc = jest.fn();
|
|
422
445
|
|
|
423
|
-
triggerFetch
|
|
424
|
-
.mockImplementationOnce( () => POST_RESPONSE )
|
|
425
|
-
.mockImplementationOnce( () => STALE_SAVE_RESPONSE );
|
|
446
|
+
triggerFetch.mockImplementation( () => TERM_RESPONSE );
|
|
426
447
|
|
|
427
448
|
await getEntityRecord(
|
|
428
|
-
'
|
|
429
|
-
'
|
|
449
|
+
'taxonomy',
|
|
450
|
+
'category',
|
|
430
451
|
1
|
|
431
452
|
)( {
|
|
432
453
|
dispatch,
|
|
@@ -436,27 +457,13 @@ describe( 'getEntityRecord', () => {
|
|
|
436
457
|
|
|
437
458
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
438
459
|
|
|
439
|
-
handlers.persistCRDTDoc();
|
|
440
|
-
await Promise.resolve();
|
|
441
|
-
await savePromise;
|
|
460
|
+
await handlers.persistCRDTDoc();
|
|
442
461
|
|
|
443
|
-
expect(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
);
|
|
449
|
-
expect( syncManager.update ).toHaveBeenCalledWith(
|
|
450
|
-
'postType/post',
|
|
451
|
-
1,
|
|
452
|
-
{},
|
|
453
|
-
'local-undo-ignored',
|
|
454
|
-
{ isSave: true }
|
|
455
|
-
);
|
|
456
|
-
expect( liveSyncState ).toEqual( {
|
|
457
|
-
isSaved: true,
|
|
458
|
-
title: SYNCED_TITLE,
|
|
459
|
-
} );
|
|
462
|
+
expect(
|
|
463
|
+
resolveSelectWithSync.getEditedEntityRecord
|
|
464
|
+
).not.toHaveBeenCalled();
|
|
465
|
+
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
466
|
+
expect( syncManager.createPersistedCRDTDoc ).not.toHaveBeenCalled();
|
|
460
467
|
} );
|
|
461
468
|
|
|
462
469
|
it( 'provides transient properties when read/write config is supplied', async () => {
|
|
@@ -507,6 +514,7 @@ describe( 'getEntityRecord', () => {
|
|
|
507
514
|
addUndoMeta: expect.any( Function ),
|
|
508
515
|
editRecord: expect.any( Function ),
|
|
509
516
|
getEditedRecord: expect.any( Function ),
|
|
517
|
+
onUndoStackChange: expect.any( Function ),
|
|
510
518
|
onStatusChange: expect.any( Function ),
|
|
511
519
|
persistCRDTDoc: expect.any( Function ),
|
|
512
520
|
refetchRecord: expect.any( Function ),
|
package/src/test/selectors.js
CHANGED
|
@@ -24,7 +24,55 @@ import {
|
|
|
24
24
|
getRevisions,
|
|
25
25
|
getRevision,
|
|
26
26
|
hasRevision,
|
|
27
|
+
hasUndo,
|
|
28
|
+
hasRedo,
|
|
27
29
|
} from '../selectors';
|
|
30
|
+
import { getSyncManager } from '../sync';
|
|
31
|
+
|
|
32
|
+
jest.mock( '../sync', () => ( {
|
|
33
|
+
getSyncManager: jest.fn(),
|
|
34
|
+
} ) );
|
|
35
|
+
|
|
36
|
+
describe( 'hasUndo/hasRedo', () => {
|
|
37
|
+
afterEach( () => {
|
|
38
|
+
getSyncManager.mockReset();
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
it( 'reads undo availability from core-data state when a sync undo manager is available', () => {
|
|
42
|
+
const undoManager = {
|
|
43
|
+
hasUndo: jest.fn( () => false ),
|
|
44
|
+
hasRedo: jest.fn( () => false ),
|
|
45
|
+
};
|
|
46
|
+
getSyncManager.mockReturnValue( { undoManager } );
|
|
47
|
+
|
|
48
|
+
const state = deepFreeze( {
|
|
49
|
+
syncUndoManagerState: {
|
|
50
|
+
hasRedo: true,
|
|
51
|
+
hasUndo: true,
|
|
52
|
+
},
|
|
53
|
+
} );
|
|
54
|
+
|
|
55
|
+
expect( hasUndo( state ) ).toBe( true );
|
|
56
|
+
expect( hasRedo( state ) ).toBe( true );
|
|
57
|
+
expect( undoManager.hasUndo ).not.toHaveBeenCalled();
|
|
58
|
+
expect( undoManager.hasRedo ).not.toHaveBeenCalled();
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
it( 'falls back to the default undo manager when no sync undo manager is available', () => {
|
|
62
|
+
const undoManager = {
|
|
63
|
+
hasUndo: jest.fn( () => true ),
|
|
64
|
+
hasRedo: jest.fn( () => false ),
|
|
65
|
+
};
|
|
66
|
+
getSyncManager.mockReturnValue( undefined );
|
|
67
|
+
|
|
68
|
+
const state = { undoManager };
|
|
69
|
+
|
|
70
|
+
expect( hasUndo( state ) ).toBe( true );
|
|
71
|
+
expect( hasRedo( state ) ).toBe( false );
|
|
72
|
+
expect( undoManager.hasUndo ).toHaveBeenCalled();
|
|
73
|
+
expect( undoManager.hasRedo ).toHaveBeenCalled();
|
|
74
|
+
} );
|
|
75
|
+
} );
|
|
28
76
|
|
|
29
77
|
describe( 'getEntityRecord', () => {
|
|
30
78
|
describe( 'normalizing Post ID passed as recordKey', () => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import fastDeepEqual from 'fast-deep-equal/es6/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a copy of `edits` where any value equal to its persisted counterpart
|
|
8
|
+
* is set to `undefined`. The edits reducer treats `undefined` as a signal to
|
|
9
|
+
* drop the edit, so the property is no longer considered dirty.
|
|
10
|
+
*
|
|
11
|
+
* @param edits Edits keyed by property name.
|
|
12
|
+
* @param persistedRecord Persisted entity record to compare against.
|
|
13
|
+
*
|
|
14
|
+
* @return Edits with unchanged properties set to `undefined`.
|
|
15
|
+
*/
|
|
16
|
+
export default function clearUnchangedEdits(
|
|
17
|
+
edits: Record< string, unknown >,
|
|
18
|
+
persistedRecord: Record< string, any > | undefined
|
|
19
|
+
): Record< string, unknown > {
|
|
20
|
+
if ( ! persistedRecord ) {
|
|
21
|
+
return edits;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
Object.entries( edits ).map( ( [ key, value ] ) => {
|
|
26
|
+
const persisted =
|
|
27
|
+
persistedRecord[ key ]?.raw ?? persistedRecord[ key ];
|
|
28
|
+
return [
|
|
29
|
+
key,
|
|
30
|
+
fastDeepEqual( value, persisted ) ? undefined : value,
|
|
31
|
+
];
|
|
32
|
+
} )
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/utils/crdt-blocks.ts
CHANGED
|
@@ -69,6 +69,10 @@ export type YBlocks = Y.Array< YBlock >;
|
|
|
69
69
|
// Attribute values will be typed as the union of `Y.Text` and `unknown`.
|
|
70
70
|
export type YBlockAttributes = Y.Map< Y.Text | unknown >;
|
|
71
71
|
|
|
72
|
+
interface MergeCrdtBlocksOptions {
|
|
73
|
+
preserveClientIds?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
/**
|
|
73
77
|
* Optional description of where a cursor falls.
|
|
74
78
|
*
|
|
@@ -420,11 +424,13 @@ function createNewYBlock( block: Block ): YBlock {
|
|
|
420
424
|
* @param attributeCursor When provided, describes a selection cursor falling within a
|
|
421
425
|
* RichText field associated with a specific block and attribute.
|
|
422
426
|
* Derived from the changes that produced the blocks.
|
|
427
|
+
* @param options Optional settings for the merge operation.
|
|
423
428
|
*/
|
|
424
429
|
export function mergeCrdtBlocks(
|
|
425
430
|
yblocks: YBlocks,
|
|
426
431
|
incomingBlocks: Block[],
|
|
427
|
-
attributeCursor: MergeCursorPosition
|
|
432
|
+
attributeCursor: MergeCursorPosition,
|
|
433
|
+
options: MergeCrdtBlocksOptions = {}
|
|
428
434
|
): void {
|
|
429
435
|
// Ensure we are working with serializable block data.
|
|
430
436
|
if ( ! serializableBlocksCache.has( incomingBlocks ) ) {
|
|
@@ -594,32 +600,31 @@ export function mergeCrdtBlocks(
|
|
|
594
600
|
mergeCrdtBlocks(
|
|
595
601
|
yInnerBlocks,
|
|
596
602
|
incomingBlockPropertyValue ?? [],
|
|
597
|
-
attributeCursor
|
|
603
|
+
attributeCursor,
|
|
604
|
+
options
|
|
598
605
|
);
|
|
599
606
|
break;
|
|
600
607
|
}
|
|
601
608
|
|
|
602
609
|
case 'clientId': {
|
|
603
|
-
//
|
|
604
|
-
//
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
//
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
// which is consistent with areBlocksEqual ignoring
|
|
622
|
-
// clientId when diffing.
|
|
610
|
+
// Code Editor changes reparse raw HTML on every
|
|
611
|
+
// keystroke and regenerate fresh clientIds. Keep Y.Doc
|
|
612
|
+
// clientIds stable for the code editor so peers do not
|
|
613
|
+
// remount unchanged blocks on every edit.
|
|
614
|
+
if ( options.preserveClientIds ) {
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Otherwise, accept new clientIds from updates
|
|
619
|
+
if (
|
|
620
|
+
incomingBlockPropertyValue !==
|
|
621
|
+
localYBlock.get( incomingBlockProperty )
|
|
622
|
+
) {
|
|
623
|
+
localYBlock.set(
|
|
624
|
+
incomingBlockProperty,
|
|
625
|
+
incomingBlockPropertyValue
|
|
626
|
+
);
|
|
627
|
+
}
|
|
623
628
|
break;
|
|
624
629
|
}
|
|
625
630
|
|
package/src/utils/crdt.ts
CHANGED
package/src/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as clearUnchangedEdits } from './clear-unchanged-edits';
|
|
1
2
|
export { default as conservativeMapItem } from './conservative-map-item';
|
|
2
3
|
export { default as getNormalizedCommaSeparable } from './get-normalized-comma-separable';
|
|
3
4
|
export { default as ifMatchingAction } from './if-matching-action';
|
|
@@ -14,3 +15,4 @@ export {
|
|
|
14
15
|
} from './user-permissions';
|
|
15
16
|
export { RECEIVE_INTERMEDIATE_RESULTS } from './receive-intermediate-results';
|
|
16
17
|
export { default as normalizeQueryForResolution } from './normalize-query-for-resolution';
|
|
18
|
+
export { saveCRDTDoc } from './save-crdt-doc';
|