@wordpress/core-data 7.40.2-next.v.202602241322.0 → 7.40.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -27
- package/build/actions.cjs +9 -26
- package/build/actions.cjs.map +2 -2
- package/build/awareness/post-editor-awareness.cjs +45 -6
- package/build/awareness/post-editor-awareness.cjs.map +2 -2
- package/build/awareness/types.cjs.map +1 -1
- package/build/entities.cjs +50 -23
- package/build/entities.cjs.map +2 -2
- package/build/entity-provider.cjs +15 -6
- package/build/entity-provider.cjs.map +2 -2
- package/build/hooks/use-entity-prop.cjs +33 -2
- package/build/hooks/use-entity-prop.cjs.map +2 -2
- package/build/hooks/use-post-editor-awareness-state.cjs +120 -1
- package/build/hooks/use-post-editor-awareness-state.cjs.map +2 -2
- package/build/index.cjs +0 -3
- package/build/index.cjs.map +2 -2
- package/build/private-actions.cjs +27 -3
- package/build/private-actions.cjs.map +2 -2
- package/build/private-apis.cjs +14 -5
- package/build/private-apis.cjs.map +3 -3
- package/build/private-selectors.cjs +22 -2
- package/build/private-selectors.cjs.map +2 -2
- package/build/queried-data/actions.cjs +1 -1
- package/build/queried-data/actions.cjs.map +2 -2
- package/build/queried-data/reducer.cjs +19 -13
- package/build/queried-data/reducer.cjs.map +2 -2
- package/build/reducer.cjs +19 -2
- package/build/reducer.cjs.map +2 -2
- package/build/resolvers.cjs +16 -6
- package/build/resolvers.cjs.map +2 -2
- package/build/selectors.cjs +0 -15
- package/build/selectors.cjs.map +2 -2
- package/build/sync.cjs +11 -5
- package/build/sync.cjs.map +2 -2
- package/build/types.cjs.map +1 -1
- package/build/utils/block-selection-history.cjs +1 -1
- package/build/utils/block-selection-history.cjs.map +2 -2
- package/build/utils/crdt-blocks.cjs +125 -49
- package/build/utils/crdt-blocks.cjs.map +2 -2
- package/build/utils/crdt-selection.cjs +50 -19
- package/build/utils/crdt-selection.cjs.map +2 -2
- package/build/utils/crdt-text.cjs +52 -0
- package/build/utils/crdt-text.cjs.map +7 -0
- package/build/utils/crdt-user-selections.cjs +16 -6
- package/build/utils/crdt-user-selections.cjs.map +3 -3
- package/build/utils/crdt-utils.cjs +54 -2
- package/build/utils/crdt-utils.cjs.map +2 -2
- package/build/utils/crdt.cjs +38 -29
- package/build/utils/crdt.cjs.map +2 -2
- package/build-module/actions.mjs +14 -26
- package/build-module/actions.mjs.map +2 -2
- package/build-module/awareness/post-editor-awareness.mjs +47 -7
- package/build-module/awareness/post-editor-awareness.mjs.map +2 -2
- package/build-module/entities.mjs +53 -24
- package/build-module/entities.mjs.map +2 -2
- package/build-module/entity-provider.mjs +15 -6
- package/build-module/entity-provider.mjs.map +2 -2
- package/build-module/hooks/use-entity-prop.mjs +34 -3
- package/build-module/hooks/use-entity-prop.mjs.map +2 -2
- package/build-module/hooks/use-post-editor-awareness-state.mjs +117 -1
- package/build-module/hooks/use-post-editor-awareness-state.mjs.map +2 -2
- package/build-module/index.mjs +0 -2
- package/build-module/index.mjs.map +2 -2
- package/build-module/private-actions.mjs +24 -2
- package/build-module/private-actions.mjs.map +2 -2
- package/build-module/private-apis.mjs +21 -6
- package/build-module/private-apis.mjs.map +3 -3
- package/build-module/private-selectors.mjs +19 -1
- package/build-module/private-selectors.mjs.map +2 -2
- package/build-module/queried-data/actions.mjs +1 -1
- package/build-module/queried-data/actions.mjs.map +2 -2
- package/build-module/queried-data/reducer.mjs +19 -13
- package/build-module/queried-data/reducer.mjs.map +2 -2
- package/build-module/reducer.mjs +18 -2
- package/build-module/reducer.mjs.map +2 -2
- package/build-module/resolvers.mjs +16 -6
- package/build-module/resolvers.mjs.map +2 -2
- package/build-module/selectors.mjs +0 -14
- package/build-module/selectors.mjs.map +2 -2
- package/build-module/sync.mjs +7 -3
- package/build-module/sync.mjs.map +2 -2
- package/build-module/utils/block-selection-history.mjs +5 -2
- package/build-module/utils/block-selection-history.mjs.map +2 -2
- package/build-module/utils/crdt-blocks.mjs +124 -49
- package/build-module/utils/crdt-blocks.mjs.map +2 -2
- package/build-module/utils/crdt-selection.mjs +53 -20
- package/build-module/utils/crdt-selection.mjs.map +2 -2
- package/build-module/utils/crdt-text.mjs +26 -0
- package/build-module/utils/crdt-text.mjs.map +7 -0
- package/build-module/utils/crdt-user-selections.mjs +16 -7
- package/build-module/utils/crdt-user-selections.mjs.map +3 -3
- package/build-module/utils/crdt-utils.mjs +51 -1
- package/build-module/utils/crdt-utils.mjs.map +2 -2
- package/build-module/utils/crdt.mjs +42 -34
- package/build-module/utils/crdt.mjs.map +2 -2
- package/build-types/actions.d.ts +0 -11
- package/build-types/actions.d.ts.map +1 -1
- package/build-types/awareness/post-editor-awareness.d.ts +2 -2
- package/build-types/awareness/post-editor-awareness.d.ts.map +1 -1
- package/build-types/awareness/types.d.ts +6 -1
- package/build-types/awareness/types.d.ts.map +1 -1
- package/build-types/entities.d.ts +20 -1
- package/build-types/entities.d.ts.map +1 -1
- package/build-types/entity-provider.d.ts +11 -6
- package/build-types/entity-provider.d.ts.map +1 -1
- package/build-types/hooks/use-entity-prop.d.ts.map +1 -1
- package/build-types/hooks/use-post-editor-awareness-state.d.ts +40 -7
- package/build-types/hooks/use-post-editor-awareness-state.d.ts.map +1 -1
- package/build-types/index.d.ts +0 -3
- package/build-types/index.d.ts.map +1 -1
- package/build-types/parsed-blocks-cache.d.ts +10 -0
- package/build-types/parsed-blocks-cache.d.ts.map +1 -0
- package/build-types/private-actions.d.ts +12 -0
- package/build-types/private-actions.d.ts.map +1 -1
- package/build-types/private-apis.d.ts +21 -1
- package/build-types/private-apis.d.ts.map +1 -1
- package/build-types/private-selectors.d.ts +17 -0
- package/build-types/private-selectors.d.ts.map +1 -1
- package/build-types/queried-data/reducer.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 +1 -9
- package/build-types/selectors.d.ts.map +1 -1
- package/build-types/sync.d.ts +2 -2
- package/build-types/sync.d.ts.map +1 -1
- package/build-types/types.d.ts +11 -2
- package/build-types/types.d.ts.map +1 -1
- package/build-types/utils/block-selection-history.d.ts.map +1 -1
- package/build-types/utils/crdt-blocks.d.ts +12 -1
- package/build-types/utils/crdt-blocks.d.ts.map +1 -1
- package/build-types/utils/crdt-selection.d.ts +10 -0
- package/build-types/utils/crdt-selection.d.ts.map +1 -1
- package/build-types/utils/crdt-text.d.ts +16 -0
- package/build-types/utils/crdt-text.d.ts.map +1 -0
- package/build-types/utils/crdt-user-selections.d.ts +17 -4
- package/build-types/utils/crdt-user-selections.d.ts.map +1 -1
- package/build-types/utils/crdt-utils.d.ts +20 -0
- package/build-types/utils/crdt-utils.d.ts.map +1 -1
- package/build-types/utils/crdt.d.ts +14 -7
- package/build-types/utils/crdt.d.ts.map +1 -1
- package/build-types/utils/normalize-query-for-resolution.d.ts +12 -0
- package/build-types/utils/normalize-query-for-resolution.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/build-types/utils/test/crdt-utils.d.ts +2 -0
- package/build-types/utils/test/crdt-utils.d.ts.map +1 -0
- package/package.json +18 -18
- package/src/actions.js +26 -41
- package/src/awareness/post-editor-awareness.ts +106 -7
- package/src/awareness/test/post-editor-awareness.ts +50 -10
- package/src/awareness/types.ts +7 -1
- package/src/entities.js +62 -24
- package/src/entity-provider.js +24 -11
- package/src/hooks/test/use-post-editor-awareness-state.ts +446 -3
- package/src/hooks/use-entity-prop.js +43 -3
- package/src/hooks/use-post-editor-awareness-state.ts +229 -7
- package/src/index.js +0 -6
- package/src/private-actions.js +43 -1
- package/src/{private-apis.js → private-apis.ts} +21 -3
- package/src/private-selectors.ts +43 -0
- package/src/queried-data/actions.js +1 -1
- package/src/queried-data/reducer.js +26 -14
- package/src/reducer.js +36 -1
- package/src/resolvers.js +23 -7
- package/src/selectors.ts +1 -32
- package/src/sync.ts +6 -2
- package/src/test/entities.js +231 -14
- package/src/test/private-actions.js +1 -1
- package/src/test/resolvers.js +64 -11
- package/src/test/store.js +116 -0
- package/src/types.ts +21 -2
- package/src/utils/block-selection-history.ts +5 -2
- package/src/utils/crdt-blocks.ts +250 -81
- package/src/utils/crdt-selection.ts +92 -26
- package/src/utils/crdt-text.ts +43 -0
- package/src/utils/crdt-user-selections.ts +29 -8
- package/src/utils/crdt-utils.ts +99 -0
- package/src/utils/crdt.ts +72 -46
- package/src/utils/test/crdt-blocks.ts +1177 -22
- package/src/utils/test/crdt-user-selections.ts +5 -0
- package/src/utils/test/crdt-utils.ts +387 -0
- package/src/utils/test/crdt.ts +423 -60
package/src/test/entities.js
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
import apiFetch from '@wordpress/api-fetch';
|
|
5
5
|
|
|
6
6
|
jest.mock( '@wordpress/api-fetch' );
|
|
7
|
+
jest.mock( '../sync', () => ( {
|
|
8
|
+
...jest.requireActual( '../sync' ),
|
|
9
|
+
getSyncManager: jest.fn(),
|
|
10
|
+
} ) );
|
|
11
|
+
jest.mock( '../utils/crdt', () => ( {
|
|
12
|
+
...jest.requireActual( '../utils/crdt' ),
|
|
13
|
+
applyPostChangesToCRDTDoc: jest.fn(),
|
|
14
|
+
} ) );
|
|
7
15
|
|
|
8
16
|
/**
|
|
9
17
|
* Internal dependencies
|
|
@@ -14,6 +22,11 @@ import {
|
|
|
14
22
|
prePersistPostType,
|
|
15
23
|
additionalEntityConfigLoaders,
|
|
16
24
|
} from '../entities';
|
|
25
|
+
import { getSyncManager } from '../sync';
|
|
26
|
+
import {
|
|
27
|
+
applyPostChangesToCRDTDoc,
|
|
28
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
29
|
+
} from '../utils/crdt';
|
|
17
30
|
|
|
18
31
|
describe( 'getMethodName', () => {
|
|
19
32
|
it( 'should return the right method name for an entity with the root kind', () => {
|
|
@@ -45,52 +58,256 @@ describe( 'getMethodName', () => {
|
|
|
45
58
|
} );
|
|
46
59
|
|
|
47
60
|
describe( 'prePersistPostType', () => {
|
|
48
|
-
it( 'set the status to draft and empty the title when saving auto-draft posts', () => {
|
|
61
|
+
it( 'set the status to draft and empty the title when saving auto-draft posts', async () => {
|
|
49
62
|
let record = {
|
|
50
63
|
status: 'auto-draft',
|
|
51
64
|
};
|
|
52
65
|
const edits = {};
|
|
53
|
-
expect(
|
|
66
|
+
expect(
|
|
67
|
+
await prePersistPostType( record, edits, 'post', false )
|
|
68
|
+
).toEqual( {
|
|
54
69
|
status: 'draft',
|
|
55
70
|
title: '',
|
|
56
|
-
meta: {},
|
|
57
71
|
} );
|
|
58
72
|
|
|
59
73
|
record = {
|
|
60
74
|
status: 'publish',
|
|
61
75
|
};
|
|
62
|
-
expect(
|
|
63
|
-
|
|
64
|
-
} );
|
|
76
|
+
expect(
|
|
77
|
+
await prePersistPostType( record, edits, 'post', false )
|
|
78
|
+
).toEqual( {} );
|
|
65
79
|
|
|
66
80
|
record = {
|
|
67
81
|
status: 'auto-draft',
|
|
68
82
|
title: 'Auto Draft',
|
|
69
83
|
};
|
|
70
|
-
expect(
|
|
84
|
+
expect(
|
|
85
|
+
await prePersistPostType( record, edits, 'post', false )
|
|
86
|
+
).toEqual( {
|
|
71
87
|
status: 'draft',
|
|
72
88
|
title: '',
|
|
73
|
-
meta: {},
|
|
74
89
|
} );
|
|
75
90
|
|
|
76
91
|
record = {
|
|
77
92
|
status: 'publish',
|
|
78
93
|
title: 'My Title',
|
|
79
94
|
};
|
|
80
|
-
expect(
|
|
81
|
-
|
|
82
|
-
} );
|
|
95
|
+
expect(
|
|
96
|
+
await prePersistPostType( record, edits, 'post', false )
|
|
97
|
+
).toEqual( {} );
|
|
83
98
|
} );
|
|
84
99
|
|
|
85
|
-
it( 'does not set the status to draft and empty the title when saving templates', () => {
|
|
100
|
+
it( 'does not set the status to draft and empty the title when saving templates', async () => {
|
|
86
101
|
const record = {
|
|
87
102
|
status: 'auto-draft',
|
|
88
103
|
title: 'Auto Draft',
|
|
89
104
|
};
|
|
90
105
|
const edits = {};
|
|
91
|
-
expect(
|
|
92
|
-
|
|
106
|
+
expect(
|
|
107
|
+
await prePersistPostType( record, edits, 'post', true )
|
|
108
|
+
).toEqual( {} );
|
|
109
|
+
} );
|
|
110
|
+
|
|
111
|
+
it( 'adds meta with serialized CRDT doc when createPersistedCRDTDoc returns a value', async () => {
|
|
112
|
+
const mockSerializedDoc = 'serialized-crdt-doc-data';
|
|
113
|
+
getSyncManager.mockReturnValue( {
|
|
114
|
+
createPersistedCRDTDoc: jest
|
|
115
|
+
.fn()
|
|
116
|
+
.mockReturnValue( mockSerializedDoc ),
|
|
117
|
+
} );
|
|
118
|
+
|
|
119
|
+
const record = { id: 123, status: 'publish' };
|
|
120
|
+
const edits = {};
|
|
121
|
+
const result = await prePersistPostType( record, edits, 'post', false );
|
|
122
|
+
|
|
123
|
+
expect( result.meta ).toEqual( {
|
|
124
|
+
[ POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE ]: mockSerializedDoc,
|
|
93
125
|
} );
|
|
126
|
+
|
|
127
|
+
expect( getSyncManager ).toHaveBeenCalled();
|
|
128
|
+
expect( getSyncManager().createPersistedCRDTDoc ).toHaveBeenCalledWith(
|
|
129
|
+
'postType/post',
|
|
130
|
+
123
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
getSyncManager.mockReset();
|
|
134
|
+
} );
|
|
135
|
+
} );
|
|
136
|
+
|
|
137
|
+
describe( 'loadPostTypeEntities', () => {
|
|
138
|
+
let originalCollaborationEnabled;
|
|
139
|
+
|
|
140
|
+
beforeEach( () => {
|
|
141
|
+
apiFetch.mockReset();
|
|
142
|
+
applyPostChangesToCRDTDoc.mockReset();
|
|
143
|
+
originalCollaborationEnabled = window._wpCollaborationEnabled;
|
|
144
|
+
} );
|
|
145
|
+
|
|
146
|
+
afterEach( () => {
|
|
147
|
+
window._wpCollaborationEnabled = originalCollaborationEnabled;
|
|
148
|
+
} );
|
|
149
|
+
|
|
150
|
+
it( 'should include custom taxonomy rest_bases in synced properties when collaboration is enabled', async () => {
|
|
151
|
+
window._wpCollaborationEnabled = true;
|
|
152
|
+
|
|
153
|
+
const mockPostTypes = {
|
|
154
|
+
book: {
|
|
155
|
+
name: 'Books',
|
|
156
|
+
rest_base: 'books',
|
|
157
|
+
rest_namespace: 'wp/v2',
|
|
158
|
+
taxonomies: [ 'genre', 'audience' ],
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const mockTaxonomies = {
|
|
162
|
+
genre: {
|
|
163
|
+
name: 'Genres',
|
|
164
|
+
rest_base: 'genres',
|
|
165
|
+
rest_namespace: 'wp/v2',
|
|
166
|
+
},
|
|
167
|
+
audience: {
|
|
168
|
+
name: 'Audiences',
|
|
169
|
+
rest_base: 'audiences',
|
|
170
|
+
rest_namespace: 'wp/v2',
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
apiFetch
|
|
175
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
176
|
+
.mockResolvedValueOnce( mockTaxonomies );
|
|
177
|
+
|
|
178
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
179
|
+
( loader ) => loader.kind === 'postType'
|
|
180
|
+
);
|
|
181
|
+
const entities = await postTypeLoader.loadEntities();
|
|
182
|
+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
|
|
183
|
+
|
|
184
|
+
bookEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
185
|
+
|
|
186
|
+
expect( applyPostChangesToCRDTDoc ).toHaveBeenCalledWith(
|
|
187
|
+
{},
|
|
188
|
+
{},
|
|
189
|
+
expect.any( Set )
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
193
|
+
expect( syncedProperties ).toContain( 'genres' );
|
|
194
|
+
expect( syncedProperties ).toContain( 'audiences' );
|
|
195
|
+
} );
|
|
196
|
+
|
|
197
|
+
it( 'should not fetch taxonomies when collaboration is disabled', async () => {
|
|
198
|
+
window._wpCollaborationEnabled = false;
|
|
199
|
+
|
|
200
|
+
const mockPostTypes = {
|
|
201
|
+
post: {
|
|
202
|
+
name: 'Posts',
|
|
203
|
+
rest_base: 'posts',
|
|
204
|
+
rest_namespace: 'wp/v2',
|
|
205
|
+
taxonomies: [ 'category', 'post_tag' ],
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
apiFetch.mockResolvedValueOnce( mockPostTypes );
|
|
210
|
+
|
|
211
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
212
|
+
( loader ) => loader.kind === 'postType'
|
|
213
|
+
);
|
|
214
|
+
const entities = await postTypeLoader.loadEntities();
|
|
215
|
+
const postEntity = entities.find( ( e ) => e.name === 'post' );
|
|
216
|
+
|
|
217
|
+
postEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
218
|
+
|
|
219
|
+
// Only one apiFetch call (post types), no taxonomy fetch.
|
|
220
|
+
expect( apiFetch ).toHaveBeenCalledTimes( 1 );
|
|
221
|
+
|
|
222
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
223
|
+
expect( syncedProperties ).not.toContain( 'categories' );
|
|
224
|
+
expect( syncedProperties ).not.toContain( 'tags' );
|
|
225
|
+
} );
|
|
226
|
+
|
|
227
|
+
it( 'should skip taxonomy rest_base when taxonomy is not found in fetched taxonomies', async () => {
|
|
228
|
+
window._wpCollaborationEnabled = true;
|
|
229
|
+
|
|
230
|
+
const mockPostTypes = {
|
|
231
|
+
book: {
|
|
232
|
+
name: 'Books',
|
|
233
|
+
rest_base: 'books',
|
|
234
|
+
rest_namespace: 'wp/v2',
|
|
235
|
+
taxonomies: [ 'genre', 'missing_taxonomy' ],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const mockTaxonomies = {
|
|
239
|
+
genre: {
|
|
240
|
+
name: 'Genres',
|
|
241
|
+
rest_base: 'genres',
|
|
242
|
+
rest_namespace: 'wp/v2',
|
|
243
|
+
},
|
|
244
|
+
// 'missing_taxonomy' is intentionally absent.
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
apiFetch
|
|
248
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
249
|
+
.mockResolvedValueOnce( mockTaxonomies );
|
|
250
|
+
|
|
251
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
252
|
+
( loader ) => loader.kind === 'postType'
|
|
253
|
+
);
|
|
254
|
+
const entities = await postTypeLoader.loadEntities();
|
|
255
|
+
const bookEntity = entities.find( ( e ) => e.name === 'book' );
|
|
256
|
+
|
|
257
|
+
bookEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
258
|
+
|
|
259
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
260
|
+
expect( syncedProperties ).toContain( 'genres' );
|
|
261
|
+
// missing_taxonomy has no rest_base entry, so nothing should be added for it.
|
|
262
|
+
expect( syncedProperties.size ).toBe( 16 ); // 15 base + 1 taxonomy (genres)
|
|
263
|
+
} );
|
|
264
|
+
|
|
265
|
+
it( 'should include base synced properties regardless of taxonomies', async () => {
|
|
266
|
+
window._wpCollaborationEnabled = true;
|
|
267
|
+
|
|
268
|
+
const mockPostTypes = {
|
|
269
|
+
page: {
|
|
270
|
+
name: 'Pages',
|
|
271
|
+
rest_base: 'pages',
|
|
272
|
+
rest_namespace: 'wp/v2',
|
|
273
|
+
taxonomies: [],
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
apiFetch
|
|
278
|
+
.mockResolvedValueOnce( mockPostTypes )
|
|
279
|
+
.mockResolvedValueOnce( {} );
|
|
280
|
+
|
|
281
|
+
const postTypeLoader = additionalEntityConfigLoaders.find(
|
|
282
|
+
( loader ) => loader.kind === 'postType'
|
|
283
|
+
);
|
|
284
|
+
const entities = await postTypeLoader.loadEntities();
|
|
285
|
+
const pageEntity = entities.find( ( e ) => e.name === 'page' );
|
|
286
|
+
|
|
287
|
+
pageEntity.syncConfig.applyChangesToCRDTDoc( {}, {} );
|
|
288
|
+
|
|
289
|
+
const syncedProperties = applyPostChangesToCRDTDoc.mock.calls[ 0 ][ 2 ];
|
|
290
|
+
const expectedBase = [
|
|
291
|
+
'author',
|
|
292
|
+
'blocks',
|
|
293
|
+
'content',
|
|
294
|
+
'comment_status',
|
|
295
|
+
'date',
|
|
296
|
+
'excerpt',
|
|
297
|
+
'featured_media',
|
|
298
|
+
'format',
|
|
299
|
+
'meta',
|
|
300
|
+
'ping_status',
|
|
301
|
+
'slug',
|
|
302
|
+
'status',
|
|
303
|
+
'sticky',
|
|
304
|
+
'template',
|
|
305
|
+
'title',
|
|
306
|
+
];
|
|
307
|
+
for ( const prop of expectedBase ) {
|
|
308
|
+
expect( syncedProperties ).toContain( prop );
|
|
309
|
+
}
|
|
310
|
+
expect( syncedProperties.size ).toBe( 15 );
|
|
94
311
|
} );
|
|
95
312
|
} );
|
|
96
313
|
|
package/src/test/resolvers.js
CHANGED
|
@@ -172,16 +172,69 @@ describe( 'getEntityRecord', () => {
|
|
|
172
172
|
editRecord: expect.any( Function ),
|
|
173
173
|
getEditedRecord: expect.any( Function ),
|
|
174
174
|
onStatusChange: expect.any( Function ),
|
|
175
|
+
persistCRDTDoc: expect.any( Function ),
|
|
175
176
|
refetchRecord: expect.any( Function ),
|
|
176
177
|
restoreUndoMeta: expect.any( Function ),
|
|
177
|
-
saveRecord: expect.any( Function ),
|
|
178
178
|
}
|
|
179
179
|
);
|
|
180
180
|
} );
|
|
181
181
|
|
|
182
|
-
it( '
|
|
183
|
-
const
|
|
184
|
-
const EDITED_RECORD = { id: 1, title: 'Edited
|
|
182
|
+
it( 'persistCRDTDoc fetches edited record and does not save full entity record when the entity does not support meta', async () => {
|
|
183
|
+
const ENTITY_RECORD = { id: 1, title: 'Test Record' };
|
|
184
|
+
const EDITED_RECORD = { id: 1, title: 'Edited Record' };
|
|
185
|
+
const ENTITY_RESPONSE = {
|
|
186
|
+
json: () => Promise.resolve( ENTITY_RECORD ),
|
|
187
|
+
};
|
|
188
|
+
const ENTITIES_WITH_SYNC = [
|
|
189
|
+
{
|
|
190
|
+
name: 'bar',
|
|
191
|
+
kind: 'foo',
|
|
192
|
+
baseURL: '/wp/v2/foo',
|
|
193
|
+
baseURLParams: { context: 'edit' },
|
|
194
|
+
syncConfig: {},
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
dispatch.saveEntityRecord = jest.fn();
|
|
199
|
+
|
|
200
|
+
const resolveSelectWithSync = {
|
|
201
|
+
getEntitiesConfig: jest.fn( () => ENTITIES_WITH_SYNC ),
|
|
202
|
+
getEditedEntityRecord: jest.fn( () =>
|
|
203
|
+
Promise.resolve( EDITED_RECORD )
|
|
204
|
+
),
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
triggerFetch.mockImplementation( () => ENTITY_RESPONSE );
|
|
208
|
+
|
|
209
|
+
await getEntityRecord(
|
|
210
|
+
'foo',
|
|
211
|
+
'bar',
|
|
212
|
+
1
|
|
213
|
+
)( {
|
|
214
|
+
dispatch,
|
|
215
|
+
registry,
|
|
216
|
+
resolveSelect: resolveSelectWithSync,
|
|
217
|
+
} );
|
|
218
|
+
|
|
219
|
+
// Extract the handlers passed to syncManager.load.
|
|
220
|
+
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
221
|
+
|
|
222
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
223
|
+
handlers.persistCRDTDoc();
|
|
224
|
+
await resolveSelectWithSync.getEditedEntityRecord();
|
|
225
|
+
|
|
226
|
+
// Should have fetched the full edited entity record.
|
|
227
|
+
expect(
|
|
228
|
+
resolveSelectWithSync.getEditedEntityRecord
|
|
229
|
+
).toHaveBeenCalledWith( 'foo', 'bar', 1 );
|
|
230
|
+
|
|
231
|
+
// Should not have called saveEntityRecord.
|
|
232
|
+
expect( dispatch.saveEntityRecord ).not.toHaveBeenCalled();
|
|
233
|
+
} );
|
|
234
|
+
|
|
235
|
+
it( 'persistCRDTDoc fetches edited record and saves full entity record', async () => {
|
|
236
|
+
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
237
|
+
const EDITED_RECORD = { id: 1, title: 'Edited Post', meta: {} };
|
|
185
238
|
const POST_RESPONSE = {
|
|
186
239
|
json: () => Promise.resolve( POST_RECORD ),
|
|
187
240
|
};
|
|
@@ -219,8 +272,8 @@ describe( 'getEntityRecord', () => {
|
|
|
219
272
|
// Extract the handlers passed to syncManager.load.
|
|
220
273
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
221
274
|
|
|
222
|
-
// Call
|
|
223
|
-
handlers.
|
|
275
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
276
|
+
handlers.persistCRDTDoc();
|
|
224
277
|
await resolveSelectWithSync.getEditedEntityRecord();
|
|
225
278
|
|
|
226
279
|
// Should have fetched the full edited entity record.
|
|
@@ -236,8 +289,8 @@ describe( 'getEntityRecord', () => {
|
|
|
236
289
|
);
|
|
237
290
|
} );
|
|
238
291
|
|
|
239
|
-
it( '
|
|
240
|
-
const POST_RECORD = { id: 1, title: 'Test Post' };
|
|
292
|
+
it( 'persistCRDTDoc saves even when there are no unsaved edits', async () => {
|
|
293
|
+
const POST_RECORD = { id: 1, title: 'Test Post', meta: {} };
|
|
241
294
|
const POST_RESPONSE = {
|
|
242
295
|
json: () => Promise.resolve( POST_RECORD ),
|
|
243
296
|
};
|
|
@@ -275,8 +328,8 @@ describe( 'getEntityRecord', () => {
|
|
|
275
328
|
|
|
276
329
|
const handlers = syncManager.load.mock.calls[ 0 ][ 4 ];
|
|
277
330
|
|
|
278
|
-
// Call
|
|
279
|
-
handlers.
|
|
331
|
+
// Call persistCRDTDoc and wait for the internal promise chain.
|
|
332
|
+
handlers.persistCRDTDoc();
|
|
280
333
|
await resolveSelectWithSync.getEditedEntityRecord();
|
|
281
334
|
|
|
282
335
|
// Should save the record even with no edits (the whole point of the fix).
|
|
@@ -336,9 +389,9 @@ describe( 'getEntityRecord', () => {
|
|
|
336
389
|
editRecord: expect.any( Function ),
|
|
337
390
|
getEditedRecord: expect.any( Function ),
|
|
338
391
|
onStatusChange: expect.any( Function ),
|
|
392
|
+
persistCRDTDoc: expect.any( Function ),
|
|
339
393
|
refetchRecord: expect.any( Function ),
|
|
340
394
|
restoreUndoMeta: expect.any( Function ),
|
|
341
|
-
saveRecord: expect.any( Function ),
|
|
342
395
|
}
|
|
343
396
|
);
|
|
344
397
|
} );
|
package/src/test/store.js
CHANGED
|
@@ -113,6 +113,122 @@ describe( 'getEntityRecord', () => {
|
|
|
113
113
|
} );
|
|
114
114
|
} );
|
|
115
115
|
|
|
116
|
+
describe( 'getEntityRecords', () => {
|
|
117
|
+
const POSTS = [
|
|
118
|
+
createTestPost( 1 ),
|
|
119
|
+
createTestPost( 2 ),
|
|
120
|
+
createTestPost( 3 ),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
let registry;
|
|
124
|
+
|
|
125
|
+
beforeEach( () => {
|
|
126
|
+
registry = createTestRegistry();
|
|
127
|
+
triggerFetch.mockReset();
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
it( 'preserves collection when getEntityRecord resolves after getEntityRecords', async () => {
|
|
131
|
+
let resolveSlowFetch;
|
|
132
|
+
const slowFetchPromise = new Promise( ( resolve ) => {
|
|
133
|
+
resolveSlowFetch = resolve;
|
|
134
|
+
} );
|
|
135
|
+
|
|
136
|
+
triggerFetch.mockImplementation( ( { path } ) => {
|
|
137
|
+
// Single post fetch (e.g. /wp/v2/posts/1): return slow promise.
|
|
138
|
+
if ( /\/wp\/v2\/posts\/\d+/.test( path ) ) {
|
|
139
|
+
return slowFetchPromise;
|
|
140
|
+
}
|
|
141
|
+
// Collection fetch: return immediately.
|
|
142
|
+
return Promise.resolve( {
|
|
143
|
+
json: () => Promise.resolve( POSTS ),
|
|
144
|
+
headers: {
|
|
145
|
+
get: ( header ) => {
|
|
146
|
+
if ( header === 'X-WP-Total' ) {
|
|
147
|
+
return String( POSTS.length );
|
|
148
|
+
}
|
|
149
|
+
if ( header === 'X-WP-TotalPages' ) {
|
|
150
|
+
return '1';
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
} );
|
|
156
|
+
} );
|
|
157
|
+
|
|
158
|
+
const resolveSelectStore = registry.resolveSelect( coreDataStore );
|
|
159
|
+
|
|
160
|
+
// Start getEntityRecord first (slow), then getEntityRecords (fast).
|
|
161
|
+
const singlePromise = resolveSelectStore.getEntityRecord(
|
|
162
|
+
'postType',
|
|
163
|
+
'post',
|
|
164
|
+
1,
|
|
165
|
+
{ context: 'edit' }
|
|
166
|
+
);
|
|
167
|
+
await resolveSelectStore.getEntityRecords( 'postType', 'post', {
|
|
168
|
+
context: 'edit',
|
|
169
|
+
} );
|
|
170
|
+
|
|
171
|
+
// Now resolve the slow single-record fetch.
|
|
172
|
+
resolveSlowFetch( {
|
|
173
|
+
json: () => Promise.resolve( POSTS[ 0 ] ),
|
|
174
|
+
headers: { get: () => null },
|
|
175
|
+
} );
|
|
176
|
+
await singlePromise;
|
|
177
|
+
|
|
178
|
+
// Wait for all pending thunks to settle.
|
|
179
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
|
|
180
|
+
|
|
181
|
+
const allPosts = registry
|
|
182
|
+
.select( coreDataStore )
|
|
183
|
+
.getEntityRecords( 'postType', 'post', { context: 'edit' } );
|
|
184
|
+
expect( allPosts.map( ( p ) => p.id ) ).toEqual( [ 1, 2, 3 ] );
|
|
185
|
+
} );
|
|
186
|
+
|
|
187
|
+
it( 'preserves collection when getEntityRecord is called after getEntityRecords', async () => {
|
|
188
|
+
triggerFetch.mockImplementation( ( { path } ) => {
|
|
189
|
+
// Collection fetch.
|
|
190
|
+
if ( ! /\/wp\/v2\/posts\/\d+/.test( path ) ) {
|
|
191
|
+
return Promise.resolve( {
|
|
192
|
+
json: () => Promise.resolve( POSTS ),
|
|
193
|
+
headers: {
|
|
194
|
+
get: ( header ) => {
|
|
195
|
+
if ( header === 'X-WP-Total' ) {
|
|
196
|
+
return String( POSTS.length );
|
|
197
|
+
}
|
|
198
|
+
if ( header === 'X-WP-TotalPages' ) {
|
|
199
|
+
return '1';
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
} );
|
|
205
|
+
}
|
|
206
|
+
// Single post fetch.
|
|
207
|
+
return Promise.resolve( {
|
|
208
|
+
json: () => Promise.resolve( POSTS[ 0 ] ),
|
|
209
|
+
headers: { get: () => null },
|
|
210
|
+
} );
|
|
211
|
+
} );
|
|
212
|
+
|
|
213
|
+
const resolveSelectStore = registry.resolveSelect( coreDataStore );
|
|
214
|
+
|
|
215
|
+
// First resolve the collection.
|
|
216
|
+
await resolveSelectStore.getEntityRecords( 'postType', 'post', {
|
|
217
|
+
context: 'edit',
|
|
218
|
+
} );
|
|
219
|
+
|
|
220
|
+
// Then resolve a single record.
|
|
221
|
+
await resolveSelectStore.getEntityRecord( 'postType', 'post', 1, {
|
|
222
|
+
context: 'edit',
|
|
223
|
+
} );
|
|
224
|
+
|
|
225
|
+
const allPosts = registry
|
|
226
|
+
.select( coreDataStore )
|
|
227
|
+
.getEntityRecords( 'postType', 'post', { context: 'edit' } );
|
|
228
|
+
expect( allPosts.map( ( p ) => p.id ) ).toEqual( [ 1, 2, 3 ] );
|
|
229
|
+
} );
|
|
230
|
+
} );
|
|
231
|
+
|
|
116
232
|
describe( 'clearEntityRecordEdits', () => {
|
|
117
233
|
let registry;
|
|
118
234
|
|
package/src/types.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import type { Y } from '@wordpress/sync';
|
|
4
|
+
import type { ConnectionStatusDisconnected, Y } from '@wordpress/sync';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
SelectionType,
|
|
11
|
+
SelectionDirection,
|
|
12
|
+
} from './utils/crdt-user-selections';
|
|
13
|
+
|
|
14
|
+
export type { ConnectionStatus } from '@wordpress/sync';
|
|
15
|
+
|
|
16
|
+
export type ConnectionError = NonNullable<
|
|
17
|
+
ConnectionStatusDisconnected[ 'error' ]
|
|
18
|
+
>;
|
|
10
19
|
|
|
11
20
|
export interface AnyFunction {
|
|
12
21
|
( ...args: any[] ): any;
|
|
@@ -44,6 +53,7 @@ export interface WPBlockSelection {
|
|
|
44
53
|
export interface WPSelection {
|
|
45
54
|
selectionEnd: WPBlockSelection;
|
|
46
55
|
selectionStart: WPBlockSelection;
|
|
56
|
+
initialPosition?: number | null;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -85,6 +95,8 @@ export type SelectionInOneBlock = {
|
|
|
85
95
|
type: SelectionType.SelectionInOneBlock;
|
|
86
96
|
cursorStartPosition: CursorPosition;
|
|
87
97
|
cursorEndPosition: CursorPosition;
|
|
98
|
+
// The direction of the selection, indicating where the caret sits.
|
|
99
|
+
selectionDirection?: SelectionDirection;
|
|
88
100
|
};
|
|
89
101
|
|
|
90
102
|
export type SelectionInMultipleBlocks = {
|
|
@@ -94,6 +106,8 @@ export type SelectionInMultipleBlocks = {
|
|
|
94
106
|
type: SelectionType.SelectionInMultipleBlocks;
|
|
95
107
|
cursorStartPosition: CursorPosition;
|
|
96
108
|
cursorEndPosition: CursorPosition;
|
|
109
|
+
// The direction of the selection, indicating where the caret sits.
|
|
110
|
+
selectionDirection?: SelectionDirection;
|
|
97
111
|
};
|
|
98
112
|
|
|
99
113
|
export type SelectionWholeBlock = {
|
|
@@ -110,3 +124,8 @@ export type SelectionState =
|
|
|
110
124
|
| SelectionInOneBlock
|
|
111
125
|
| SelectionInMultipleBlocks
|
|
112
126
|
| SelectionWholeBlock;
|
|
127
|
+
|
|
128
|
+
export interface ResolvedSelection {
|
|
129
|
+
richTextOffset: number | null;
|
|
130
|
+
localClientId: string | null;
|
|
131
|
+
}
|
|
@@ -9,7 +9,10 @@ import { Y } from '@wordpress/sync';
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal dependencies
|
|
11
11
|
*/
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
findBlockByClientIdInDoc,
|
|
14
|
+
richTextOffsetToHtmlIndex,
|
|
15
|
+
} from './crdt-utils';
|
|
13
16
|
import type { WPBlockSelection, WPSelection } from '../types';
|
|
14
17
|
|
|
15
18
|
// Default size for selection history (not including current selection)
|
|
@@ -163,7 +166,7 @@ function convertWPBlockSelectionToSelection(
|
|
|
163
166
|
const offset = selection.offset ?? 0;
|
|
164
167
|
const relativePosition = Y.createRelativePositionFromTypeIndex(
|
|
165
168
|
changedYText,
|
|
166
|
-
offset
|
|
169
|
+
richTextOffsetToHtmlIndex( changedYText.toString(), offset )
|
|
167
170
|
);
|
|
168
171
|
|
|
169
172
|
return {
|