@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/utils/test/crdt.ts
CHANGED
|
@@ -8,22 +8,117 @@ import { Y } from '@wordpress/sync';
|
|
|
8
8
|
*/
|
|
9
9
|
import { describe, expect, it, jest, beforeEach } from '@jest/globals';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Mock getBlockTypes so isRichTextAttribute can identify rich-text attrs.
|
|
13
|
+
*/
|
|
14
|
+
jest.mock( '@wordpress/blocks', () => {
|
|
15
|
+
const actual = jest.requireActual( '@wordpress/blocks' ) as Record<
|
|
16
|
+
string,
|
|
17
|
+
unknown
|
|
18
|
+
>;
|
|
19
|
+
return {
|
|
20
|
+
...actual,
|
|
21
|
+
getBlockTypes: () => [
|
|
22
|
+
{
|
|
23
|
+
name: 'core/paragraph',
|
|
24
|
+
attributes: { content: { type: 'rich-text' } },
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'core/table',
|
|
28
|
+
attributes: {
|
|
29
|
+
hasFixedLayout: { type: 'boolean' },
|
|
30
|
+
caption: { type: 'rich-text' },
|
|
31
|
+
body: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
query: {
|
|
34
|
+
cells: {
|
|
35
|
+
type: 'array',
|
|
36
|
+
query: {
|
|
37
|
+
content: { type: 'rich-text' },
|
|
38
|
+
tag: { type: 'string' },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* WordPress dependencies
|
|
51
|
+
*/
|
|
52
|
+
import { RichTextData } from '@wordpress/rich-text';
|
|
53
|
+
|
|
11
54
|
/**
|
|
12
55
|
* Internal dependencies
|
|
13
56
|
*/
|
|
14
|
-
import {
|
|
15
|
-
CRDT_RECORD_MAP_KEY,
|
|
16
|
-
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
17
|
-
} from '../../sync';
|
|
57
|
+
import { CRDT_RECORD_MAP_KEY } from '../../sync';
|
|
18
58
|
import {
|
|
19
59
|
applyPostChangesToCRDTDoc,
|
|
60
|
+
defaultCollectionSyncConfig,
|
|
20
61
|
getPostChangesFromCRDTDoc,
|
|
62
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
21
63
|
type PostChanges,
|
|
22
64
|
type YPostRecord,
|
|
23
65
|
} from '../crdt';
|
|
24
|
-
import type { YBlock, YBlocks } from '../crdt-blocks';
|
|
66
|
+
import type { YBlock, YBlockRecord, YBlocks } from '../crdt-blocks';
|
|
67
|
+
import { updateSelectionHistory } from '../crdt-selection';
|
|
25
68
|
import { createYMap, getRootMap, type YMapWrap } from '../crdt-utils';
|
|
26
|
-
import type { Post
|
|
69
|
+
import type { Post } from '../../entity-types';
|
|
70
|
+
|
|
71
|
+
// Default synced properties matching the base set built in entities.js,
|
|
72
|
+
// plus 'categories' and 'tags' as example taxonomy rest_base values.
|
|
73
|
+
const defaultSyncedProperties = new Set< string >( [
|
|
74
|
+
'blocks',
|
|
75
|
+
'categories',
|
|
76
|
+
'content',
|
|
77
|
+
'date',
|
|
78
|
+
'excerpt',
|
|
79
|
+
'meta',
|
|
80
|
+
'slug',
|
|
81
|
+
'status',
|
|
82
|
+
'tags',
|
|
83
|
+
'title',
|
|
84
|
+
] );
|
|
85
|
+
|
|
86
|
+
describe( 'defaultCollectionSyncConfig', () => {
|
|
87
|
+
it( 'has no-op applyChangesToCRDTDoc', () => {
|
|
88
|
+
const doc = new Y.Doc();
|
|
89
|
+
// Should not throw and return undefined.
|
|
90
|
+
expect(
|
|
91
|
+
defaultCollectionSyncConfig.applyChangesToCRDTDoc( doc, {
|
|
92
|
+
title: 'test',
|
|
93
|
+
} )
|
|
94
|
+
).toBeUndefined();
|
|
95
|
+
doc.destroy();
|
|
96
|
+
} );
|
|
97
|
+
|
|
98
|
+
it( 'has getChangesFromCRDTDoc that returns empty object', () => {
|
|
99
|
+
const doc = new Y.Doc();
|
|
100
|
+
const result = defaultCollectionSyncConfig.getChangesFromCRDTDoc( doc, {
|
|
101
|
+
title: 'test',
|
|
102
|
+
} );
|
|
103
|
+
expect( result ).toEqual( {} );
|
|
104
|
+
doc.destroy();
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
it( 'shouldSync returns true when objectId is null (collection)', () => {
|
|
108
|
+
expect(
|
|
109
|
+
defaultCollectionSyncConfig.shouldSync?.( 'comment', null )
|
|
110
|
+
).toBe( true );
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
it( 'shouldSync returns false when objectId is provided (individual record)', () => {
|
|
114
|
+
expect(
|
|
115
|
+
defaultCollectionSyncConfig.shouldSync?.( 'comment', '123' )
|
|
116
|
+
).toBe( false );
|
|
117
|
+
expect(
|
|
118
|
+
defaultCollectionSyncConfig.shouldSync?.( 'comment', 'foo' )
|
|
119
|
+
).toBe( false );
|
|
120
|
+
} );
|
|
121
|
+
} );
|
|
27
122
|
|
|
28
123
|
describe( 'crdt', () => {
|
|
29
124
|
let doc: Y.Doc;
|
|
@@ -38,8 +133,6 @@ describe( 'crdt', () => {
|
|
|
38
133
|
} );
|
|
39
134
|
|
|
40
135
|
describe( 'applyPostChangesToCRDTDoc', () => {
|
|
41
|
-
const mockPostType = {} as Type;
|
|
42
|
-
|
|
43
136
|
let map: YMapWrap< YPostRecord >;
|
|
44
137
|
|
|
45
138
|
beforeEach( () => {
|
|
@@ -51,7 +144,7 @@ describe( 'crdt', () => {
|
|
|
51
144
|
title: 'New Title',
|
|
52
145
|
} as PostChanges;
|
|
53
146
|
|
|
54
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
147
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
55
148
|
|
|
56
149
|
const title = map.get( 'title' );
|
|
57
150
|
expect( title ).toBeInstanceOf( Y.Text );
|
|
@@ -64,7 +157,7 @@ describe( 'crdt', () => {
|
|
|
64
157
|
unsyncedProperty: 'value',
|
|
65
158
|
} as unknown as PostChanges;
|
|
66
159
|
|
|
67
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
160
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
68
161
|
|
|
69
162
|
expect( map.has( 'unsyncedProperty' ) ).toBe( false );
|
|
70
163
|
expect( map.get( 'title' )?.toString() ).toBe( 'New Title' );
|
|
@@ -75,7 +168,7 @@ describe( 'crdt', () => {
|
|
|
75
168
|
title: () => 'function value',
|
|
76
169
|
} as unknown as PostChanges;
|
|
77
170
|
|
|
78
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
171
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
79
172
|
|
|
80
173
|
expect( map.has( 'title' ) ).toBe( false );
|
|
81
174
|
} );
|
|
@@ -85,7 +178,7 @@ describe( 'crdt', () => {
|
|
|
85
178
|
title: { raw: 'Raw Title', rendered: 'Rendered Title' },
|
|
86
179
|
};
|
|
87
180
|
|
|
88
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
181
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
89
182
|
|
|
90
183
|
const title = map.get( 'title' );
|
|
91
184
|
expect( title ).toBeInstanceOf( Y.Text );
|
|
@@ -97,7 +190,7 @@ describe( 'crdt', () => {
|
|
|
97
190
|
title: 'Auto Draft',
|
|
98
191
|
} as PostChanges;
|
|
99
192
|
|
|
100
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
193
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
101
194
|
|
|
102
195
|
const title = map.get( 'title' );
|
|
103
196
|
expect( title ).toBeInstanceOf( Y.Text );
|
|
@@ -109,7 +202,7 @@ describe( 'crdt', () => {
|
|
|
109
202
|
applyPostChangesToCRDTDoc(
|
|
110
203
|
doc,
|
|
111
204
|
{ title: '' } as PostChanges,
|
|
112
|
-
|
|
205
|
+
defaultSyncedProperties
|
|
113
206
|
);
|
|
114
207
|
|
|
115
208
|
const title = map.get( 'title' );
|
|
@@ -120,7 +213,7 @@ describe( 'crdt', () => {
|
|
|
120
213
|
applyPostChangesToCRDTDoc(
|
|
121
214
|
doc,
|
|
122
215
|
{ title: 'Auto Draft' } as PostChanges,
|
|
123
|
-
|
|
216
|
+
defaultSyncedProperties
|
|
124
217
|
);
|
|
125
218
|
|
|
126
219
|
expect( map.get( 'title' )?.toString() ).toBe( '' );
|
|
@@ -135,7 +228,7 @@ describe( 'crdt', () => {
|
|
|
135
228
|
},
|
|
136
229
|
};
|
|
137
230
|
|
|
138
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
231
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
139
232
|
|
|
140
233
|
const excerpt = map.get( 'excerpt' );
|
|
141
234
|
expect( excerpt ).toBeInstanceOf( Y.Text );
|
|
@@ -147,7 +240,7 @@ describe( 'crdt', () => {
|
|
|
147
240
|
slug: '',
|
|
148
241
|
};
|
|
149
242
|
|
|
150
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
243
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
151
244
|
|
|
152
245
|
expect( map.has( 'slug' ) ).toBe( false );
|
|
153
246
|
} );
|
|
@@ -157,7 +250,7 @@ describe( 'crdt', () => {
|
|
|
157
250
|
slug: 'my-post-slug',
|
|
158
251
|
};
|
|
159
252
|
|
|
160
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
253
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
161
254
|
|
|
162
255
|
expect( map.get( 'slug' ) ).toBe( 'my-post-slug' );
|
|
163
256
|
} );
|
|
@@ -175,7 +268,7 @@ describe( 'crdt', () => {
|
|
|
175
268
|
],
|
|
176
269
|
};
|
|
177
270
|
|
|
178
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
271
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
179
272
|
|
|
180
273
|
expect( ( map.get( 'blocks' ) as YBlocks ).toJSON() ).toEqual(
|
|
181
274
|
changes.blocks
|
|
@@ -187,7 +280,7 @@ describe( 'crdt', () => {
|
|
|
187
280
|
blocks: [],
|
|
188
281
|
};
|
|
189
282
|
|
|
190
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
283
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
191
284
|
|
|
192
285
|
const blocks = map.get( 'blocks' );
|
|
193
286
|
expect( blocks ).toBeInstanceOf( Y.Array );
|
|
@@ -201,7 +294,7 @@ describe( 'crdt', () => {
|
|
|
201
294
|
blocks: undefined,
|
|
202
295
|
};
|
|
203
296
|
|
|
204
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
297
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
205
298
|
|
|
206
299
|
// The key should still exist, but the value should be undefined.
|
|
207
300
|
expect( map.has( 'blocks' ) ).toBe( true );
|
|
@@ -213,7 +306,7 @@ describe( 'crdt', () => {
|
|
|
213
306
|
content: 'Hello, world!',
|
|
214
307
|
} as PostChanges;
|
|
215
308
|
|
|
216
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
309
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
217
310
|
|
|
218
311
|
const content = map.get( 'content' );
|
|
219
312
|
expect( content ).toBeInstanceOf( Y.Text );
|
|
@@ -228,7 +321,7 @@ describe( 'crdt', () => {
|
|
|
228
321
|
},
|
|
229
322
|
} as PostChanges;
|
|
230
323
|
|
|
231
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
324
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
232
325
|
|
|
233
326
|
const content = map.get( 'content' );
|
|
234
327
|
expect( content ).toBeInstanceOf( Y.Text );
|
|
@@ -242,7 +335,7 @@ describe( 'crdt', () => {
|
|
|
242
335
|
applyPostChangesToCRDTDoc(
|
|
243
336
|
doc,
|
|
244
337
|
{ title: 'Old Title' } as PostChanges,
|
|
245
|
-
|
|
338
|
+
defaultSyncedProperties
|
|
246
339
|
);
|
|
247
340
|
const titleRef = map.get( 'title' );
|
|
248
341
|
|
|
@@ -250,7 +343,7 @@ describe( 'crdt', () => {
|
|
|
250
343
|
applyPostChangesToCRDTDoc(
|
|
251
344
|
doc,
|
|
252
345
|
{ title: 'New Title' } as PostChanges,
|
|
253
|
-
|
|
346
|
+
defaultSyncedProperties
|
|
254
347
|
);
|
|
255
348
|
|
|
256
349
|
expect( map.get( 'title' ) ).toBe( titleRef );
|
|
@@ -262,7 +355,7 @@ describe( 'crdt', () => {
|
|
|
262
355
|
applyPostChangesToCRDTDoc(
|
|
263
356
|
doc,
|
|
264
357
|
{ content: 'Old content' } as PostChanges,
|
|
265
|
-
|
|
358
|
+
defaultSyncedProperties
|
|
266
359
|
);
|
|
267
360
|
const contentRef = map.get( 'content' );
|
|
268
361
|
|
|
@@ -270,7 +363,7 @@ describe( 'crdt', () => {
|
|
|
270
363
|
applyPostChangesToCRDTDoc(
|
|
271
364
|
doc,
|
|
272
365
|
{ content: 'New content' } as PostChanges,
|
|
273
|
-
|
|
366
|
+
defaultSyncedProperties
|
|
274
367
|
);
|
|
275
368
|
|
|
276
369
|
expect( map.get( 'content' ) ).toBe( contentRef );
|
|
@@ -282,7 +375,7 @@ describe( 'crdt', () => {
|
|
|
282
375
|
applyPostChangesToCRDTDoc(
|
|
283
376
|
doc,
|
|
284
377
|
{ excerpt: 'Old excerpt' } as PostChanges,
|
|
285
|
-
|
|
378
|
+
defaultSyncedProperties
|
|
286
379
|
);
|
|
287
380
|
const excerptRef = map.get( 'excerpt' );
|
|
288
381
|
|
|
@@ -290,7 +383,7 @@ describe( 'crdt', () => {
|
|
|
290
383
|
applyPostChangesToCRDTDoc(
|
|
291
384
|
doc,
|
|
292
385
|
{ excerpt: 'New excerpt' } as PostChanges,
|
|
293
|
-
|
|
386
|
+
defaultSyncedProperties
|
|
294
387
|
);
|
|
295
388
|
|
|
296
389
|
expect( map.get( 'excerpt' ) ).toBe( excerptRef );
|
|
@@ -308,7 +401,7 @@ describe( 'crdt', () => {
|
|
|
308
401
|
metaMap.set( 'some_meta', 'old value' );
|
|
309
402
|
map.set( 'meta', metaMap );
|
|
310
403
|
|
|
311
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
404
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
312
405
|
|
|
313
406
|
expect( metaMap.get( 'some_meta' ) ).toBe( 'new value' );
|
|
314
407
|
} );
|
|
@@ -324,7 +417,7 @@ describe( 'crdt', () => {
|
|
|
324
417
|
metaMap.set( 'some_meta', 'old value' );
|
|
325
418
|
map.set( 'meta', metaMap );
|
|
326
419
|
|
|
327
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
420
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
328
421
|
|
|
329
422
|
expect( metaMap.get( 'some_meta' ) ).toStrictEqual( [
|
|
330
423
|
'value',
|
|
@@ -339,23 +432,40 @@ describe( 'crdt', () => {
|
|
|
339
432
|
},
|
|
340
433
|
};
|
|
341
434
|
|
|
342
|
-
applyPostChangesToCRDTDoc( doc, changes,
|
|
435
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
343
436
|
|
|
344
437
|
const metaMap = map.get( 'meta' );
|
|
345
438
|
expect( metaMap ).toBeInstanceOf( Y.Map );
|
|
346
439
|
expect( metaMap?.get( 'custom_field' ) ).toBe( 'value' );
|
|
347
440
|
} );
|
|
441
|
+
|
|
442
|
+
it( 'syncs taxonomy rest_base values included in syncedProperties', () => {
|
|
443
|
+
const changes = {
|
|
444
|
+
categories: [ 1, 2, 3 ],
|
|
445
|
+
genre: [ 10, 20 ], // should be ignored
|
|
446
|
+
tags: [ 4, 5 ],
|
|
447
|
+
} as unknown as PostChanges;
|
|
448
|
+
|
|
449
|
+
applyPostChangesToCRDTDoc( doc, changes, defaultSyncedProperties );
|
|
450
|
+
|
|
451
|
+
expect( map.get( 'categories' ) ).toEqual( [ 1, 2, 3 ] );
|
|
452
|
+
expect( map.get( 'genre' ) ).toBeUndefined();
|
|
453
|
+
expect( map.get( 'tags' ) ).toEqual( [ 4, 5 ] );
|
|
454
|
+
|
|
455
|
+
const customSyncedProperties = new Set( [
|
|
456
|
+
...defaultSyncedProperties,
|
|
457
|
+
'genre', // now included
|
|
458
|
+
] );
|
|
459
|
+
|
|
460
|
+
applyPostChangesToCRDTDoc( doc, changes, customSyncedProperties );
|
|
461
|
+
|
|
462
|
+
expect( map.get( 'categories' ) ).toEqual( [ 1, 2, 3 ] );
|
|
463
|
+
expect( map.get( 'genre' ) ).toEqual( [ 10, 20 ] );
|
|
464
|
+
expect( map.get( 'tags' ) ).toEqual( [ 4, 5 ] );
|
|
465
|
+
} );
|
|
348
466
|
} );
|
|
349
467
|
|
|
350
468
|
describe( 'getPostChangesFromCRDTDoc', () => {
|
|
351
|
-
const mockPostType = {
|
|
352
|
-
slug: 'post',
|
|
353
|
-
supports: {
|
|
354
|
-
title: true,
|
|
355
|
-
editor: true,
|
|
356
|
-
},
|
|
357
|
-
} as unknown as Type;
|
|
358
|
-
|
|
359
469
|
let map: YMapWrap< YPostRecord >;
|
|
360
470
|
|
|
361
471
|
beforeEach( () => {
|
|
@@ -374,7 +484,7 @@ describe( 'crdt', () => {
|
|
|
374
484
|
const changes = getPostChangesFromCRDTDoc(
|
|
375
485
|
doc,
|
|
376
486
|
editedRecord,
|
|
377
|
-
|
|
487
|
+
defaultSyncedProperties
|
|
378
488
|
);
|
|
379
489
|
|
|
380
490
|
expect( changes.title ).toBe( 'CRDT Title' );
|
|
@@ -389,7 +499,7 @@ describe( 'crdt', () => {
|
|
|
389
499
|
const changes = getPostChangesFromCRDTDoc(
|
|
390
500
|
doc,
|
|
391
501
|
editedRecord,
|
|
392
|
-
|
|
502
|
+
defaultSyncedProperties
|
|
393
503
|
);
|
|
394
504
|
|
|
395
505
|
expect( changes ).not.toHaveProperty( 'unsyncedProp' );
|
|
@@ -406,7 +516,7 @@ describe( 'crdt', () => {
|
|
|
406
516
|
const changes = getPostChangesFromCRDTDoc(
|
|
407
517
|
doc,
|
|
408
518
|
editedRecord,
|
|
409
|
-
|
|
519
|
+
defaultSyncedProperties
|
|
410
520
|
);
|
|
411
521
|
|
|
412
522
|
expect( changes ).not.toHaveProperty( 'status' );
|
|
@@ -425,7 +535,7 @@ describe( 'crdt', () => {
|
|
|
425
535
|
const changesWithEmptyDate = getPostChangesFromCRDTDoc(
|
|
426
536
|
doc,
|
|
427
537
|
editedRecord,
|
|
428
|
-
|
|
538
|
+
defaultSyncedProperties
|
|
429
539
|
);
|
|
430
540
|
|
|
431
541
|
expect( changesWithEmptyDate ).not.toHaveProperty( 'date' );
|
|
@@ -435,7 +545,7 @@ describe( 'crdt', () => {
|
|
|
435
545
|
const changesWithDefinedDate = getPostChangesFromCRDTDoc(
|
|
436
546
|
doc,
|
|
437
547
|
editedRecord,
|
|
438
|
-
|
|
548
|
+
defaultSyncedProperties
|
|
439
549
|
);
|
|
440
550
|
|
|
441
551
|
expect( changesWithDefinedDate ).not.toHaveProperty( 'date' );
|
|
@@ -454,7 +564,7 @@ describe( 'crdt', () => {
|
|
|
454
564
|
const changesWithEmptyDate = getPostChangesFromCRDTDoc(
|
|
455
565
|
doc,
|
|
456
566
|
editedRecord,
|
|
457
|
-
|
|
567
|
+
defaultSyncedProperties
|
|
458
568
|
);
|
|
459
569
|
|
|
460
570
|
expect( changesWithEmptyDate ).not.toHaveProperty( 'date' );
|
|
@@ -464,7 +574,7 @@ describe( 'crdt', () => {
|
|
|
464
574
|
const changesWithDefinedDate = getPostChangesFromCRDTDoc(
|
|
465
575
|
doc,
|
|
466
576
|
editedRecord,
|
|
467
|
-
|
|
577
|
+
defaultSyncedProperties
|
|
468
578
|
);
|
|
469
579
|
|
|
470
580
|
expect( changesWithDefinedDate ).not.toHaveProperty( 'date' );
|
|
@@ -480,12 +590,74 @@ describe( 'crdt', () => {
|
|
|
480
590
|
const changes = getPostChangesFromCRDTDoc(
|
|
481
591
|
doc,
|
|
482
592
|
editedRecord,
|
|
483
|
-
|
|
593
|
+
defaultSyncedProperties
|
|
484
594
|
);
|
|
485
595
|
|
|
486
596
|
expect( changes ).toHaveProperty( 'blocks' );
|
|
487
597
|
} );
|
|
488
598
|
|
|
599
|
+
it( 'returns rich-text block attributes as RichTextData, not strings', () => {
|
|
600
|
+
// Simulate User A writing a paragraph block into the CRDT doc.
|
|
601
|
+
addBlockToDoc( map, 'block-1', 'Hello world' );
|
|
602
|
+
|
|
603
|
+
// Simulate User B reading the CRDT doc with no local blocks.
|
|
604
|
+
const editedRecord = { blocks: [] } as unknown as Post;
|
|
605
|
+
|
|
606
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
607
|
+
doc,
|
|
608
|
+
editedRecord,
|
|
609
|
+
defaultSyncedProperties
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const block = ( changes.blocks as any[] )?.[ 0 ];
|
|
613
|
+
expect( block ).toBeDefined();
|
|
614
|
+
expect( block.attributes.content ).toBeInstanceOf( RichTextData );
|
|
615
|
+
expect( block.attributes.content.text ).toBe( 'Hello world' );
|
|
616
|
+
} );
|
|
617
|
+
|
|
618
|
+
it( 'returns nested rich-text in array attributes as RichTextData', () => {
|
|
619
|
+
// Add a table block to the CRDT doc with nested cell content
|
|
620
|
+
// stored as plain strings.
|
|
621
|
+
let blocks = map.get( 'blocks' );
|
|
622
|
+
if ( ! ( blocks instanceof Y.Array ) ) {
|
|
623
|
+
blocks = new Y.Array< YBlock >();
|
|
624
|
+
map.set( 'blocks', blocks );
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const tableBlock = createYMap< YBlockRecord >();
|
|
628
|
+
tableBlock.set( 'name', 'core/table' );
|
|
629
|
+
tableBlock.set( 'clientId', 'table-1' );
|
|
630
|
+
const attrs = new Y.Map();
|
|
631
|
+
attrs.set( 'body', [
|
|
632
|
+
{
|
|
633
|
+
cells: [
|
|
634
|
+
{ content: '<strong>Cell</strong>', tag: 'td' },
|
|
635
|
+
{ content: 'Plain', tag: 'td' },
|
|
636
|
+
],
|
|
637
|
+
},
|
|
638
|
+
] );
|
|
639
|
+
tableBlock.set( 'attributes', attrs );
|
|
640
|
+
tableBlock.set( 'innerBlocks', new Y.Array() );
|
|
641
|
+
( blocks as YBlocks ).push( [ tableBlock ] );
|
|
642
|
+
|
|
643
|
+
const editedRecord = { blocks: [] } as unknown as Post;
|
|
644
|
+
|
|
645
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
646
|
+
doc,
|
|
647
|
+
editedRecord,
|
|
648
|
+
defaultSyncedProperties
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const block = ( changes.blocks as any[] )?.[ 0 ];
|
|
652
|
+
expect( block ).toBeDefined();
|
|
653
|
+
|
|
654
|
+
const cell = block.attributes.body[ 0 ].cells[ 0 ];
|
|
655
|
+
expect( cell.content ).toBeInstanceOf( RichTextData );
|
|
656
|
+
expect( ( cell.content as RichTextData ).toHTMLString() ).toBe(
|
|
657
|
+
'<strong>Cell</strong>'
|
|
658
|
+
);
|
|
659
|
+
} );
|
|
660
|
+
|
|
489
661
|
it( 'includes undefined blocks in changes', () => {
|
|
490
662
|
map.set( 'blocks', undefined );
|
|
491
663
|
|
|
@@ -502,7 +674,7 @@ describe( 'crdt', () => {
|
|
|
502
674
|
const changes = getPostChangesFromCRDTDoc(
|
|
503
675
|
doc,
|
|
504
676
|
editedRecord,
|
|
505
|
-
|
|
677
|
+
defaultSyncedProperties
|
|
506
678
|
);
|
|
507
679
|
|
|
508
680
|
expect( changes ).toHaveProperty( 'blocks' );
|
|
@@ -519,7 +691,7 @@ describe( 'crdt', () => {
|
|
|
519
691
|
const changes = getPostChangesFromCRDTDoc(
|
|
520
692
|
doc,
|
|
521
693
|
editedRecord,
|
|
522
|
-
|
|
694
|
+
defaultSyncedProperties
|
|
523
695
|
);
|
|
524
696
|
|
|
525
697
|
expect( changes.content ).toBe( 'New content' );
|
|
@@ -535,7 +707,7 @@ describe( 'crdt', () => {
|
|
|
535
707
|
const changes = getPostChangesFromCRDTDoc(
|
|
536
708
|
doc,
|
|
537
709
|
editedRecord,
|
|
538
|
-
|
|
710
|
+
defaultSyncedProperties
|
|
539
711
|
);
|
|
540
712
|
|
|
541
713
|
expect( changes.content ).toBe( 'New content' );
|
|
@@ -551,7 +723,7 @@ describe( 'crdt', () => {
|
|
|
551
723
|
const changes = getPostChangesFromCRDTDoc(
|
|
552
724
|
doc,
|
|
553
725
|
editedRecord,
|
|
554
|
-
|
|
726
|
+
defaultSyncedProperties
|
|
555
727
|
);
|
|
556
728
|
|
|
557
729
|
expect( changes ).not.toHaveProperty( 'content' );
|
|
@@ -571,7 +743,7 @@ describe( 'crdt', () => {
|
|
|
571
743
|
const changes = getPostChangesFromCRDTDoc(
|
|
572
744
|
doc,
|
|
573
745
|
editedRecord,
|
|
574
|
-
|
|
746
|
+
defaultSyncedProperties
|
|
575
747
|
);
|
|
576
748
|
|
|
577
749
|
expect( changes.meta ).toEqual( {
|
|
@@ -593,7 +765,7 @@ describe( 'crdt', () => {
|
|
|
593
765
|
const changes = getPostChangesFromCRDTDoc(
|
|
594
766
|
doc,
|
|
595
767
|
editedRecord,
|
|
596
|
-
|
|
768
|
+
defaultSyncedProperties
|
|
597
769
|
);
|
|
598
770
|
|
|
599
771
|
expect( changes.meta ).toEqual( {
|
|
@@ -601,13 +773,34 @@ describe( 'crdt', () => {
|
|
|
601
773
|
} );
|
|
602
774
|
} );
|
|
603
775
|
|
|
776
|
+
it( 'excludes orphaned meta keys not present on the edited record', () => {
|
|
777
|
+
// If post meta is registered, saved (landing in a CRDT doc),
|
|
778
|
+
// then unregistered, it can permanently mark the record dirty.
|
|
779
|
+
// Orphaned values should not show up as a change.
|
|
780
|
+
const metaMap = createYMap();
|
|
781
|
+
metaMap.set( 'registered_meta', 'value' );
|
|
782
|
+
metaMap.set( 'orphaned_meta', 'stale value' );
|
|
783
|
+
map.set( 'meta', metaMap );
|
|
784
|
+
|
|
785
|
+
const editedRecord = {
|
|
786
|
+
meta: {
|
|
787
|
+
registered_meta: 'value',
|
|
788
|
+
},
|
|
789
|
+
} as unknown as Post;
|
|
790
|
+
|
|
791
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
792
|
+
doc,
|
|
793
|
+
editedRecord,
|
|
794
|
+
defaultSyncedProperties
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
expect( changes ).not.toHaveProperty( 'meta' );
|
|
798
|
+
} );
|
|
799
|
+
|
|
604
800
|
it( 'excludes disallowed meta keys in changes', () => {
|
|
605
801
|
const metaMap = createYMap();
|
|
606
802
|
metaMap.set( 'public_meta', 'new value' );
|
|
607
|
-
metaMap.set(
|
|
608
|
-
WORDPRESS_META_KEY_FOR_CRDT_DOC_PERSISTENCE,
|
|
609
|
-
'exclude me'
|
|
610
|
-
);
|
|
803
|
+
metaMap.set( POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE, 'exclude me' );
|
|
611
804
|
map.set( 'meta', metaMap );
|
|
612
805
|
|
|
613
806
|
const editedRecord = {
|
|
@@ -619,15 +812,185 @@ describe( 'crdt', () => {
|
|
|
619
812
|
const changes = getPostChangesFromCRDTDoc(
|
|
620
813
|
doc,
|
|
621
814
|
editedRecord,
|
|
622
|
-
|
|
815
|
+
defaultSyncedProperties
|
|
623
816
|
);
|
|
624
817
|
|
|
625
818
|
expect( changes.meta ).toEqual( {
|
|
626
819
|
public_meta: 'new value', // from CRDT
|
|
627
820
|
} );
|
|
628
821
|
expect( changes.meta ).not.toHaveProperty(
|
|
629
|
-
|
|
822
|
+
POST_META_KEY_FOR_CRDT_DOC_PERSISTENCE
|
|
630
823
|
);
|
|
631
824
|
} );
|
|
825
|
+
|
|
826
|
+
it( 'returns taxonomy rest_base changes when in syncedProperties', () => {
|
|
827
|
+
map.set( 'categories', [ 1, 2 ] );
|
|
828
|
+
map.set( 'genre', [ 10, 20 ] );
|
|
829
|
+
map.set( 'tags', [ 3 ] );
|
|
830
|
+
|
|
831
|
+
const editedRecord = {
|
|
832
|
+
categories: [ 1 ],
|
|
833
|
+
genre: [ 10 ], // should be ignored
|
|
834
|
+
tags: [],
|
|
835
|
+
} as unknown as Post;
|
|
836
|
+
|
|
837
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
838
|
+
doc,
|
|
839
|
+
editedRecord,
|
|
840
|
+
defaultSyncedProperties
|
|
841
|
+
) as Record< string, unknown >;
|
|
842
|
+
|
|
843
|
+
expect( changes.categories ).toEqual( [ 1, 2 ] );
|
|
844
|
+
expect( changes.genre ).toBeUndefined();
|
|
845
|
+
expect( changes.tags ).toEqual( [ 3 ] );
|
|
846
|
+
|
|
847
|
+
const customSyncedProperties = new Set( [
|
|
848
|
+
...defaultSyncedProperties,
|
|
849
|
+
'genre', // now included
|
|
850
|
+
] );
|
|
851
|
+
|
|
852
|
+
const changes2 = getPostChangesFromCRDTDoc(
|
|
853
|
+
doc,
|
|
854
|
+
editedRecord,
|
|
855
|
+
customSyncedProperties
|
|
856
|
+
) as Record< string, unknown >;
|
|
857
|
+
|
|
858
|
+
expect( changes2.categories ).toEqual( [ 1, 2 ] );
|
|
859
|
+
expect( changes2.genre ).toEqual( [ 10, 20 ] );
|
|
860
|
+
expect( changes2.tags ).toEqual( [ 3 ] );
|
|
861
|
+
} );
|
|
862
|
+
|
|
863
|
+
describe( 'selection recalculation', () => {
|
|
864
|
+
it( 'includes recalculated selection when text is inserted before cursor', () => {
|
|
865
|
+
const ytext = addBlockToDoc( map, 'block-1', 'Hello world' );
|
|
866
|
+
|
|
867
|
+
// Record a selection at offset 5 (cursor between "Hello" and " world").
|
|
868
|
+
updateSelectionHistory( doc, {
|
|
869
|
+
selectionStart: {
|
|
870
|
+
clientId: 'block-1',
|
|
871
|
+
attributeKey: 'content',
|
|
872
|
+
offset: 5,
|
|
873
|
+
},
|
|
874
|
+
selectionEnd: {
|
|
875
|
+
clientId: 'block-1',
|
|
876
|
+
attributeKey: 'content',
|
|
877
|
+
offset: 5,
|
|
878
|
+
},
|
|
879
|
+
} );
|
|
880
|
+
|
|
881
|
+
// Simulate remote insertion: insert "XXX" at position 0.
|
|
882
|
+
ytext.insert( 0, 'XXX' );
|
|
883
|
+
|
|
884
|
+
const editedRecord = {
|
|
885
|
+
title: 'CRDT Title',
|
|
886
|
+
status: 'draft',
|
|
887
|
+
blocks: [],
|
|
888
|
+
} as unknown as Post;
|
|
889
|
+
|
|
890
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
891
|
+
doc,
|
|
892
|
+
editedRecord,
|
|
893
|
+
defaultSyncedProperties
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
expect( changes.selection ).toBeDefined();
|
|
897
|
+
expect( changes.selection?.selectionStart.offset ).toBe( 8 ); // 5 + 3
|
|
898
|
+
expect( changes.selection?.selectionStart.clientId ).toBe(
|
|
899
|
+
'block-1'
|
|
900
|
+
);
|
|
901
|
+
expect( changes.selection?.selectionStart.attributeKey ).toBe(
|
|
902
|
+
'content'
|
|
903
|
+
);
|
|
904
|
+
expect( changes.selection?.selectionEnd.offset ).toBe( 8 );
|
|
905
|
+
} );
|
|
906
|
+
|
|
907
|
+
it( 'includes recalculated selection when text is deleted before cursor', () => {
|
|
908
|
+
const ytext = addBlockToDoc( map, 'block-1', 'Hello world' );
|
|
909
|
+
|
|
910
|
+
// Record a selection at offset 8 (cursor between "Hello wo" and "rld").
|
|
911
|
+
updateSelectionHistory( doc, {
|
|
912
|
+
selectionStart: {
|
|
913
|
+
clientId: 'block-1',
|
|
914
|
+
attributeKey: 'content',
|
|
915
|
+
offset: 8,
|
|
916
|
+
},
|
|
917
|
+
selectionEnd: {
|
|
918
|
+
clientId: 'block-1',
|
|
919
|
+
attributeKey: 'content',
|
|
920
|
+
offset: 8,
|
|
921
|
+
},
|
|
922
|
+
} );
|
|
923
|
+
|
|
924
|
+
// Simulate remote deletion: delete "Hello" (5 chars at position 0).
|
|
925
|
+
ytext.delete( 0, 5 );
|
|
926
|
+
|
|
927
|
+
const editedRecord = {
|
|
928
|
+
title: 'CRDT Title',
|
|
929
|
+
status: 'draft',
|
|
930
|
+
blocks: [],
|
|
931
|
+
} as unknown as Post;
|
|
932
|
+
|
|
933
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
934
|
+
doc,
|
|
935
|
+
editedRecord,
|
|
936
|
+
defaultSyncedProperties
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
expect( changes.selection ).toBeDefined();
|
|
940
|
+
expect( changes.selection?.selectionStart.offset ).toBe( 3 ); // 8 - 5
|
|
941
|
+
} );
|
|
942
|
+
|
|
943
|
+
it( 'does not include selection when selection history is empty', () => {
|
|
944
|
+
addBlockToDoc( map, 'block-1', 'Hello world' );
|
|
945
|
+
|
|
946
|
+
const editedRecord = {
|
|
947
|
+
title: 'CRDT Title',
|
|
948
|
+
status: 'draft',
|
|
949
|
+
blocks: [],
|
|
950
|
+
} as unknown as Post;
|
|
951
|
+
|
|
952
|
+
const changes = getPostChangesFromCRDTDoc(
|
|
953
|
+
doc,
|
|
954
|
+
editedRecord,
|
|
955
|
+
defaultSyncedProperties
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
expect( changes.selection ).toBeUndefined();
|
|
959
|
+
} );
|
|
960
|
+
} );
|
|
632
961
|
} );
|
|
633
962
|
} );
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Helper to create a block with a Y.Text content attribute
|
|
966
|
+
* in the CRDT document.
|
|
967
|
+
*
|
|
968
|
+
* @param map
|
|
969
|
+
* @param clientId Block client ID.
|
|
970
|
+
* @param content Initial text content.
|
|
971
|
+
* @param name Block name (default: 'core/paragraph').
|
|
972
|
+
*/
|
|
973
|
+
function addBlockToDoc(
|
|
974
|
+
map: YMapWrap< YPostRecord >,
|
|
975
|
+
clientId: string,
|
|
976
|
+
content: string,
|
|
977
|
+
name = 'core/paragraph'
|
|
978
|
+
): Y.Text {
|
|
979
|
+
let blocks = map.get( 'blocks' );
|
|
980
|
+
if ( ! ( blocks instanceof Y.Array ) ) {
|
|
981
|
+
blocks = new Y.Array< YBlock >();
|
|
982
|
+
map.set( 'blocks', blocks );
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const block = createYMap< YBlockRecord >();
|
|
986
|
+
block.set( 'name', name );
|
|
987
|
+
block.set( 'clientId', clientId );
|
|
988
|
+
const attrs = new Y.Map();
|
|
989
|
+
const ytext = new Y.Text( content );
|
|
990
|
+
attrs.set( 'content', ytext );
|
|
991
|
+
block.set( 'attributes', attrs );
|
|
992
|
+
block.set( 'innerBlocks', new Y.Array() );
|
|
993
|
+
( blocks as YBlocks ).push( [ block ] );
|
|
994
|
+
|
|
995
|
+
return ytext;
|
|
996
|
+
}
|