@wordpress/core-data 7.48.0 → 7.48.1
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/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 +4 -2
- package/build/entities.cjs.map +2 -2
- 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/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 +13 -0
- 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/utils/crdt-blocks.cjs +12 -2
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt.cjs +2 -1
- package/build/utils/crdt.cjs.map +2 -2
- package/build/utils/index.cjs +3 -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-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 +4 -2
- package/build-module/entities.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/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 +12 -0
- 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/crdt-blocks.mjs +12 -2
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +2 -1
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-module/utils/index.mjs +2 -0
- 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-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-post-editor-awareness-state.d.ts.map +1 -1
- 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 +4 -0
- package/build-types/selectors.d.ts.map +1 -1
- 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 +1 -0
- package/build-types/utils/index.d.ts.map +1 -1
- package/build-types/utils/on-sub-key.d.ts +4 -0
- package/build-types/utils/on-sub-key.d.ts.map +1 -0
- 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 +22 -20
- 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 +9 -3
- package/src/hooks/test/use-post-editor-awareness-state.ts +10 -2
- package/src/hooks/use-post-editor-awareness-state.ts +20 -7
- package/src/private-actions.js +18 -0
- package/src/private-selectors.ts +0 -12
- package/src/reducer.js +17 -0
- package/src/resolvers.js +20 -13
- package/src/selectors.ts +11 -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/crdt-blocks.ts +27 -22
- package/src/utils/crdt.ts +2 -1
- package/src/utils/index.js +1 -0
- package/src/utils/save-crdt-doc.js +64 -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/private-selectors.ts
CHANGED
|
@@ -21,18 +21,6 @@ const EMPTY_OBJECT = {};
|
|
|
21
21
|
* Returns the previous edit from the current undo offset
|
|
22
22
|
* for the entity records edits history, if any.
|
|
23
23
|
*
|
|
24
|
-
* Known Issue: Every-time state.undoManager changes, the getUndoManager
|
|
25
|
-
* private selector is called (if used within useSelect and things like that)
|
|
26
|
-
* which ensures the UI is always properly reactive. But, it's not the case with
|
|
27
|
-
* the custom "sync" undo manager.
|
|
28
|
-
*
|
|
29
|
-
* Assumption: When an undo/redo is created, other parts of the core-data state
|
|
30
|
-
* are likely changing simultaneously, which will trigger the selectors again.
|
|
31
|
-
*
|
|
32
|
-
* This issue is acceptable based on the assumption above.
|
|
33
|
-
*
|
|
34
|
-
* @see https://github.com/WordPress/gutenberg/pull/72407/files#r2580214235 for more details.
|
|
35
|
-
*
|
|
36
24
|
* @param state State tree.
|
|
37
25
|
*
|
|
38
26
|
* @return The undo manager.
|
package/src/reducer.js
CHANGED
|
@@ -460,6 +460,22 @@ export function undoManager( state = createUndoManager() ) {
|
|
|
460
460
|
return state;
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
+
// Stores a snapshot of the sync undo manager's undo/redo availability so
|
|
464
|
+
// core-data selectors can react to undo stack changes.
|
|
465
|
+
export function syncUndoManagerState(
|
|
466
|
+
state = { hasRedo: false, hasUndo: false },
|
|
467
|
+
action
|
|
468
|
+
) {
|
|
469
|
+
switch ( action.type ) {
|
|
470
|
+
case 'SYNC_UNDO_MANAGER_CHANGE':
|
|
471
|
+
return {
|
|
472
|
+
hasRedo: action.hasRedo,
|
|
473
|
+
hasUndo: action.hasUndo,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return state;
|
|
477
|
+
}
|
|
478
|
+
|
|
463
479
|
export function editsReference( state = {}, action ) {
|
|
464
480
|
switch ( action.type ) {
|
|
465
481
|
case 'EDIT_ENTITY_RECORD':
|
|
@@ -750,6 +766,7 @@ export default combineReducers( {
|
|
|
750
766
|
themeGlobalStyleRevisions,
|
|
751
767
|
entities,
|
|
752
768
|
editsReference,
|
|
769
|
+
syncUndoManagerState,
|
|
753
770
|
undoManager,
|
|
754
771
|
embedPreviews,
|
|
755
772
|
userPermissions,
|
package/src/resolvers.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
RECEIVE_INTERMEDIATE_RESULTS,
|
|
26
26
|
isNumericID,
|
|
27
27
|
normalizeQueryForResolution,
|
|
28
|
+
saveCRDTDoc,
|
|
28
29
|
} from './utils';
|
|
29
30
|
import { fetchBlockPatterns } from './fetch';
|
|
30
31
|
import { restoreSelection, getSelectionHistory } from './utils/crdt-selection';
|
|
@@ -250,9 +251,15 @@ export const getEntityRecord =
|
|
|
250
251
|
// persistence. As we add support for syncing additional entity,
|
|
251
252
|
// we'll need to revisit where persisted CRDT documents are stored.
|
|
252
253
|
persistCRDTDoc: () => {
|
|
253
|
-
|
|
254
|
+
if (
|
|
255
|
+
! entityConfig.syncConfig?.supportsPersistence
|
|
256
|
+
) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return resolveSelect
|
|
254
261
|
.getEditedEntityRecord( kind, name, key )
|
|
255
|
-
.then( ( editedRecord ) => {
|
|
262
|
+
.then( async ( editedRecord ) => {
|
|
256
263
|
// Don't persist the CRDT document if the record is still an
|
|
257
264
|
// auto-draft or if the entity does not support meta.
|
|
258
265
|
const { meta, status } = editedRecord;
|
|
@@ -260,19 +267,14 @@ export const getEntityRecord =
|
|
|
260
267
|
return;
|
|
261
268
|
}
|
|
262
269
|
|
|
263
|
-
// Trigger a minimal save to persist the CRDT document. The
|
|
264
|
-
// entity's pre-persist hooks will create the persisted CRDT
|
|
265
|
-
// document and apply it to the record's meta.
|
|
266
270
|
const entityIdKey =
|
|
267
271
|
entityConfig.key || DEFAULT_ENTITY_KEY;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
},
|
|
275
|
-
{ __unstableSkipSyncUpdate: true }
|
|
272
|
+
const entityId =
|
|
273
|
+
editedRecord[ entityIdKey ];
|
|
274
|
+
|
|
275
|
+
await saveCRDTDoc(
|
|
276
|
+
`${ kind }/${ name }`,
|
|
277
|
+
entityId
|
|
276
278
|
);
|
|
277
279
|
} );
|
|
278
280
|
},
|
|
@@ -287,6 +289,11 @@ export const getEntityRecord =
|
|
|
287
289
|
);
|
|
288
290
|
}
|
|
289
291
|
},
|
|
292
|
+
onUndoStackChange: ( undoState ) => {
|
|
293
|
+
dispatch.__unstableNotifySyncUndoManagerChange(
|
|
294
|
+
undoState
|
|
295
|
+
);
|
|
296
|
+
},
|
|
290
297
|
restoreUndoMeta: ( ydoc, meta ) => {
|
|
291
298
|
const selectionHistory =
|
|
292
299
|
meta.get( 'selectionHistory' );
|
package/src/selectors.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
isNumericID,
|
|
25
25
|
getUserPermissionCacheKey,
|
|
26
26
|
} from './utils';
|
|
27
|
+
import { getSyncManager } from './sync';
|
|
27
28
|
import type * as ET from './entity-types';
|
|
28
29
|
import logEntityDeprecation from './utils/log-entity-deprecation';
|
|
29
30
|
|
|
@@ -44,6 +45,10 @@ export interface State {
|
|
|
44
45
|
themeGlobalStyleVariations: Record< string, string >;
|
|
45
46
|
themeGlobalStyleRevisions: Record< number, Object >;
|
|
46
47
|
undoManager: UndoManager;
|
|
48
|
+
syncUndoManagerState: {
|
|
49
|
+
hasRedo: boolean;
|
|
50
|
+
hasUndo: boolean;
|
|
51
|
+
};
|
|
47
52
|
userPermissions: Record< string, boolean >;
|
|
48
53
|
users: UserState;
|
|
49
54
|
navigationFallbackId: EntityRecordKey;
|
|
@@ -1148,6 +1153,9 @@ export function getRedoEdit( state: State ): Optional< any > {
|
|
|
1148
1153
|
* @return Whether there is a previous edit or not.
|
|
1149
1154
|
*/
|
|
1150
1155
|
export function hasUndo( state: State ): boolean {
|
|
1156
|
+
if ( getSyncManager()?.undoManager ) {
|
|
1157
|
+
return state.syncUndoManagerState.hasUndo;
|
|
1158
|
+
}
|
|
1151
1159
|
return getUndoManager( state ).hasUndo();
|
|
1152
1160
|
}
|
|
1153
1161
|
|
|
@@ -1160,6 +1168,9 @@ export function hasUndo( state: State ): boolean {
|
|
|
1160
1168
|
* @return Whether there is a next edit or not.
|
|
1161
1169
|
*/
|
|
1162
1170
|
export function hasRedo( state: State ): boolean {
|
|
1171
|
+
if ( getSyncManager()?.undoManager ) {
|
|
1172
|
+
return state.syncUndoManagerState.hasRedo;
|
|
1173
|
+
}
|
|
1163
1174
|
return getUndoManager( state ).hasRedo();
|
|
1164
1175
|
}
|
|
1165
1176
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { getUndoManager } from '../private-selectors';
|
|
5
|
+
import { getSyncManager } from '../sync';
|
|
6
|
+
|
|
7
|
+
jest.mock( '../sync', () => ( {
|
|
8
|
+
getSyncManager: jest.fn(),
|
|
9
|
+
} ) );
|
|
10
|
+
|
|
11
|
+
describe( 'getUndoManager', () => {
|
|
12
|
+
afterEach( () => {
|
|
13
|
+
getSyncManager.mockReset();
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it( 'returns the sync undo manager when one is available', () => {
|
|
17
|
+
const syncUndoManager = {
|
|
18
|
+
addRecord: jest.fn(),
|
|
19
|
+
hasRedo: jest.fn(),
|
|
20
|
+
hasUndo: jest.fn(),
|
|
21
|
+
redo: jest.fn(),
|
|
22
|
+
undo: jest.fn(),
|
|
23
|
+
};
|
|
24
|
+
const fallbackUndoManager = {
|
|
25
|
+
addRecord: jest.fn(),
|
|
26
|
+
hasRedo: jest.fn(),
|
|
27
|
+
hasUndo: jest.fn(),
|
|
28
|
+
redo: jest.fn(),
|
|
29
|
+
undo: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
getSyncManager.mockReturnValue( {
|
|
32
|
+
undoManager: syncUndoManager,
|
|
33
|
+
} );
|
|
34
|
+
|
|
35
|
+
const state = {
|
|
36
|
+
undoManager: fallbackUndoManager,
|
|
37
|
+
syncUndoManagerState: {
|
|
38
|
+
hasRedo: false,
|
|
39
|
+
hasUndo: false,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
expect( getUndoManager( state ) ).toBe( syncUndoManager );
|
|
44
|
+
} );
|
|
45
|
+
|
|
46
|
+
it( 'returns the default undo manager when there is no sync undo manager', () => {
|
|
47
|
+
const fallbackUndoManager = {
|
|
48
|
+
addRecord: jest.fn(),
|
|
49
|
+
hasRedo: jest.fn(),
|
|
50
|
+
hasUndo: jest.fn(),
|
|
51
|
+
redo: jest.fn(),
|
|
52
|
+
undo: jest.fn(),
|
|
53
|
+
};
|
|
54
|
+
getSyncManager.mockReturnValue( undefined );
|
|
55
|
+
|
|
56
|
+
expect(
|
|
57
|
+
getUndoManager( {
|
|
58
|
+
undoManager: fallbackUndoManager,
|
|
59
|
+
syncUndoManagerState: {
|
|
60
|
+
hasRedo: false,
|
|
61
|
+
hasUndo: false,
|
|
62
|
+
},
|
|
63
|
+
} )
|
|
64
|
+
).toBe( fallbackUndoManager );
|
|
65
|
+
} );
|
|
66
|
+
} );
|
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 ),
|