@wordpress/core-data 7.47.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 +8 -0
- 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 +24 -22
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/core-data",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.48.1",
|
|
4
4
|
"description": "Access to and manipulation of core WordPress entities.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
},
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
|
-
"react-native": "src/index",
|
|
44
43
|
"wpScript": true,
|
|
45
44
|
"types": "build-types",
|
|
46
45
|
"sideEffects": [
|
|
@@ -49,22 +48,23 @@
|
|
|
49
48
|
"build-module/index.mjs"
|
|
50
49
|
],
|
|
51
50
|
"dependencies": {
|
|
52
|
-
"@
|
|
53
|
-
"@wordpress/
|
|
54
|
-
"@wordpress/
|
|
55
|
-
"@wordpress/
|
|
56
|
-
"@wordpress/
|
|
57
|
-
"@wordpress/
|
|
58
|
-
"@wordpress/
|
|
59
|
-
"@wordpress/
|
|
60
|
-
"@wordpress/
|
|
61
|
-
"@wordpress/
|
|
62
|
-
"@wordpress/
|
|
63
|
-
"@wordpress/
|
|
64
|
-
"@wordpress/
|
|
65
|
-
"@wordpress/
|
|
66
|
-
"@wordpress/
|
|
67
|
-
"@wordpress/
|
|
51
|
+
"@types/react": "^18.3.27",
|
|
52
|
+
"@wordpress/api-fetch": "^7.48.1",
|
|
53
|
+
"@wordpress/block-editor": "^15.21.1",
|
|
54
|
+
"@wordpress/blocks": "^15.21.1",
|
|
55
|
+
"@wordpress/compose": "^8.1.1",
|
|
56
|
+
"@wordpress/data": "^10.48.1",
|
|
57
|
+
"@wordpress/deprecated": "^4.48.1",
|
|
58
|
+
"@wordpress/element": "^8.0.1",
|
|
59
|
+
"@wordpress/html-entities": "^4.48.1",
|
|
60
|
+
"@wordpress/i18n": "^6.21.1",
|
|
61
|
+
"@wordpress/is-shallow-equal": "^5.48.1",
|
|
62
|
+
"@wordpress/private-apis": "^1.48.1",
|
|
63
|
+
"@wordpress/rich-text": "^7.48.1",
|
|
64
|
+
"@wordpress/sync": "^1.48.1",
|
|
65
|
+
"@wordpress/undo-manager": "^1.48.1",
|
|
66
|
+
"@wordpress/url": "^4.48.1",
|
|
67
|
+
"@wordpress/warning": "^3.48.1",
|
|
68
68
|
"change-case": "^4.1.2",
|
|
69
69
|
"equivalent-key-map": "^0.2.2",
|
|
70
70
|
"fast-deep-equal": "^3.1.3",
|
|
@@ -73,16 +73,18 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@jest/globals": "^30.2.0",
|
|
76
|
+
"@testing-library/dom": "^10.4.1",
|
|
77
|
+
"@testing-library/react": "^16.3.2",
|
|
76
78
|
"@types/jest": "^29.5.14",
|
|
77
79
|
"@types/node": "^20.19.39",
|
|
78
|
-
"deep-freeze": "0.0.1"
|
|
80
|
+
"deep-freeze": "^0.0.1"
|
|
79
81
|
},
|
|
80
82
|
"peerDependencies": {
|
|
81
|
-
"react": "^
|
|
82
|
-
"react-dom": "^
|
|
83
|
+
"react": "^18.0.0",
|
|
84
|
+
"react-dom": "^18.0.0"
|
|
83
85
|
},
|
|
84
86
|
"publishConfig": {
|
|
85
87
|
"access": "public"
|
|
86
88
|
},
|
|
87
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "99df7432c5c7cb83ba41146fd1f57f3c19004305"
|
|
88
90
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WordPress dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { useSelect } from '@wordpress/data';
|
|
5
5
|
import { Y } from '@wordpress/sync';
|
|
6
6
|
// @ts-ignore No exported types for block editor store selectors.
|
|
7
7
|
import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
@@ -10,17 +10,17 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
|
|
|
10
10
|
* Internal dependencies
|
|
11
11
|
*/
|
|
12
12
|
import type { AbsoluteBlockIndexPath } from '../types';
|
|
13
|
+
import { unlock } from '../lock-unlock';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* A block as represented in the block-editor store
|
|
16
|
+
* A block as represented in the block-editor store's client ID tree.
|
|
16
17
|
*
|
|
17
18
|
* This is a minimal interface covering only the fields used by RTC awareness.
|
|
18
19
|
*/
|
|
19
|
-
|
|
20
|
+
export type EditorStoreBlock = {
|
|
20
21
|
clientId: string;
|
|
21
|
-
name: string;
|
|
22
22
|
innerBlocks: EditorStoreBlock[];
|
|
23
|
-
}
|
|
23
|
+
};
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Find the block Y.Map that contains a nested Yjs type.
|
|
@@ -112,26 +112,18 @@ export function getBlockPathInYdoc(
|
|
|
112
112
|
* Navigate the block-editor store's block tree by an index path
|
|
113
113
|
* and return the local block's clientId.
|
|
114
114
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* core/post-content block (if present) and uses its inner blocks as the
|
|
118
|
-
* navigation root, so paths align with the Yjs document structure.
|
|
119
|
-
*
|
|
120
|
-
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
|
|
115
|
+
* @param path - The index path, e.g. [0, 1] for blocks[0].innerBlocks[1].
|
|
116
|
+
* @param blocks - The tree of block-editor store post contentblocks.
|
|
121
117
|
* @return The local block clientId, or null if the path is invalid.
|
|
122
118
|
*/
|
|
123
119
|
export function resolveBlockClientIdByPath(
|
|
124
|
-
path: AbsoluteBlockIndexPath
|
|
120
|
+
path: AbsoluteBlockIndexPath,
|
|
121
|
+
blocks: EditorStoreBlock[]
|
|
125
122
|
): string | null {
|
|
126
123
|
if ( path.length === 0 ) {
|
|
127
124
|
return null;
|
|
128
125
|
}
|
|
129
126
|
|
|
130
|
-
const { getBlocks } = select( blockEditorStore );
|
|
131
|
-
const postContentBlocks = getPostContentBlocks( getBlocks(), getBlocks );
|
|
132
|
-
|
|
133
|
-
let blocks = postContentBlocks;
|
|
134
|
-
|
|
135
127
|
for ( let i = 0; i < path.length; i++ ) {
|
|
136
128
|
const block = blocks[ path[ i ] ];
|
|
137
129
|
if ( ! block ) {
|
|
@@ -151,53 +143,20 @@ export function resolveBlockClientIdByPath(
|
|
|
151
143
|
* In template mode, the block tree contains template parts wrapping a
|
|
152
144
|
* core/post-content block. The Yjs document only stores the post content
|
|
153
145
|
* blocks, so we need to find the core/post-content block and use
|
|
154
|
-
*
|
|
146
|
+
* getClientIdsTree(clientId) to retrieve its inner blocks from the store.
|
|
155
147
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* but are not populated in the .innerBlocks property of the tree
|
|
160
|
-
* returned by getBlocks().
|
|
148
|
+
* Uses the private getClientIdsTree selector which depends only on
|
|
149
|
+
* state.blocks.order, avoiding unnecessary re-renders when block
|
|
150
|
+
* attributes change (which would happen with getBlocks()).
|
|
161
151
|
*
|
|
162
|
-
* @param rootBlocks - The root-level blocks from getBlocks().
|
|
163
|
-
* @param getBlocks - The getBlocks selector.
|
|
164
152
|
* @return The blocks that correspond to the Yjs document root.
|
|
165
153
|
*/
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
return getBlocks( postContentBlock.clientId );
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return rootBlocks;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Recursively search the block tree for a block with a given name.
|
|
182
|
-
*
|
|
183
|
-
* @param blocks - The blocks to search.
|
|
184
|
-
* @param name - The block name to find.
|
|
185
|
-
* @return The first matching block, or null if not found.
|
|
186
|
-
*/
|
|
187
|
-
function findBlockByName(
|
|
188
|
-
blocks: EditorStoreBlock[],
|
|
189
|
-
name: string
|
|
190
|
-
): EditorStoreBlock | null {
|
|
191
|
-
for ( const block of blocks ) {
|
|
192
|
-
if ( block.name === name ) {
|
|
193
|
-
return block;
|
|
194
|
-
}
|
|
195
|
-
if ( block.innerBlocks?.length ) {
|
|
196
|
-
const found = findBlockByName( block.innerBlocks, name );
|
|
197
|
-
if ( found ) {
|
|
198
|
-
return found;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
154
|
+
export function usePostContentBlocks(): EditorStoreBlock[] {
|
|
155
|
+
return useSelect( ( select ) => {
|
|
156
|
+
const { getBlocksByName, getClientIdsTree } = unlock(
|
|
157
|
+
select( blockEditorStore )
|
|
158
|
+
);
|
|
159
|
+
const [ postContentClientId ] = getBlocksByName( 'core/post-content' );
|
|
160
|
+
return getClientIdsTree( postContentClientId ?? '' );
|
|
161
|
+
}, [] );
|
|
203
162
|
}
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
WPBlockSelection,
|
|
38
38
|
} from '../types';
|
|
39
39
|
import type { YBlocks } from '../utils/crdt-blocks';
|
|
40
|
+
import type { EditorStoreBlock } from './block-lookup';
|
|
40
41
|
import type {
|
|
41
42
|
DebugCollaboratorData,
|
|
42
43
|
EditorState,
|
|
@@ -245,10 +246,12 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
|
|
|
245
246
|
* clientIds (e.g. in "Show Template" mode where blocks are cloned).
|
|
246
247
|
*
|
|
247
248
|
* @param selection - The selection state.
|
|
249
|
+
* @param blocks - The tree of block-editor store post content blocks.
|
|
248
250
|
* @return The rich-text offset and block client ID, or nulls if not resolvable.
|
|
249
251
|
*/
|
|
250
252
|
public convertSelectionStateToAbsolute(
|
|
251
|
-
selection: SelectionState
|
|
253
|
+
selection: SelectionState,
|
|
254
|
+
blocks: EditorStoreBlock[]
|
|
252
255
|
): ResolvedSelection {
|
|
253
256
|
if ( selection.type === SelectionType.None ) {
|
|
254
257
|
return {
|
|
@@ -273,7 +276,7 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
|
|
|
273
276
|
if ( block instanceof Y.Map ) {
|
|
274
277
|
const path = getBlockPathInYdoc( block );
|
|
275
278
|
localClientId = path
|
|
276
|
-
? resolveBlockClientIdByPath( path )
|
|
279
|
+
? resolveBlockClientIdByPath( path, blocks )
|
|
277
280
|
: null;
|
|
278
281
|
}
|
|
279
282
|
}
|
|
@@ -306,7 +309,9 @@ export class PostEditorAwareness extends BaseAwarenessState< PostEditorState > {
|
|
|
306
309
|
|
|
307
310
|
const yType = getContainingBlockYMap( absolutePosition.type );
|
|
308
311
|
const path = yType ? getBlockPathInYdoc( yType ) : null;
|
|
309
|
-
const localClientId = path
|
|
312
|
+
const localClientId = path
|
|
313
|
+
? resolveBlockClientIdByPath( path, blocks )
|
|
314
|
+
: null;
|
|
310
315
|
|
|
311
316
|
return {
|
|
312
317
|
richTextOffset: htmlIndexToRichTextOffset(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { Y } from '@wordpress/sync';
|
|
5
|
-
import {
|
|
5
|
+
import { renderHook } from '@testing-library/react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Internal dependencies
|
|
@@ -11,23 +11,45 @@ import {
|
|
|
11
11
|
getBlockPathInYdoc,
|
|
12
12
|
getContainingBlockYMap,
|
|
13
13
|
resolveBlockClientIdByPath,
|
|
14
|
+
usePostContentBlocks,
|
|
14
15
|
} from '../block-lookup';
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
import type { EditorStoreBlock } from '../block-lookup';
|
|
18
|
+
|
|
19
|
+
type MockBlock = EditorStoreBlock & {
|
|
20
|
+
name: string;
|
|
21
|
+
innerBlocks: MockBlock[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let mockGetClientIdsTree: jest.Mock;
|
|
25
|
+
|
|
26
|
+
function mockFlattenBlocks( blocks: MockBlock[] ): MockBlock[] {
|
|
27
|
+
return blocks.flatMap( ( b ) => [
|
|
28
|
+
b,
|
|
29
|
+
...mockFlattenBlocks( b.innerBlocks ),
|
|
30
|
+
] );
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
jest.mock( '../../lock-unlock', () => ( {
|
|
34
|
+
unlock: ( obj: any ) => obj,
|
|
35
|
+
} ) );
|
|
36
|
+
|
|
17
37
|
jest.mock( '@wordpress/data', () => ( {
|
|
18
|
-
|
|
38
|
+
useSelect: ( selector: Function ) =>
|
|
39
|
+
selector( () => ( {
|
|
40
|
+
getClientIdsTree: ( ...args: any[] ) =>
|
|
41
|
+
mockGetClientIdsTree( ...args ),
|
|
42
|
+
getBlocksByName: ( blockName: string ) =>
|
|
43
|
+
mockFlattenBlocks( mockGetClientIdsTree( '' ) )
|
|
44
|
+
.filter( ( b ) => b.name === blockName )
|
|
45
|
+
.map( ( b ) => b.clientId ),
|
|
46
|
+
} ) ),
|
|
19
47
|
} ) );
|
|
20
48
|
|
|
21
49
|
jest.mock( '@wordpress/block-editor', () => ( {
|
|
22
50
|
store: 'core/block-editor',
|
|
23
51
|
} ) );
|
|
24
52
|
|
|
25
|
-
type MockBlock = {
|
|
26
|
-
clientId: string;
|
|
27
|
-
name: string;
|
|
28
|
-
innerBlocks: MockBlock[];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
53
|
/**
|
|
32
54
|
* Create a Y.Map block with a clientId and empty innerBlocks, matching the
|
|
33
55
|
* shape used by the Yjs block tree.
|
|
@@ -127,40 +149,6 @@ function createNestedYDoc( {
|
|
|
127
149
|
return { ydoc, rootBlocks, innerBlocksArray };
|
|
128
150
|
}
|
|
129
151
|
|
|
130
|
-
/**
|
|
131
|
-
* Mock the block-editor store's `getBlocks` selector.
|
|
132
|
-
*
|
|
133
|
-
* When called without an argument (or undefined), returns `rootBlocks`.
|
|
134
|
-
* When called with a clientId, looks up the block by clientId and returns
|
|
135
|
-
* its innerBlocks. This mimics how `getBlocks( clientId )` works in the
|
|
136
|
-
* real store for controlled inner blocks.
|
|
137
|
-
* @param rootBlocks
|
|
138
|
-
*/
|
|
139
|
-
function mockBlockEditorStore( rootBlocks: MockBlock[] ) {
|
|
140
|
-
const allBlocks = new Map< string, MockBlock >();
|
|
141
|
-
|
|
142
|
-
function indexBlocks( blocks: MockBlock[] ) {
|
|
143
|
-
for ( const block of blocks ) {
|
|
144
|
-
allBlocks.set( block.clientId, block );
|
|
145
|
-
if ( block.innerBlocks?.length ) {
|
|
146
|
-
indexBlocks( block.innerBlocks );
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
indexBlocks( rootBlocks );
|
|
151
|
-
|
|
152
|
-
const getBlocks = jest.fn( ( rootClientId?: string ) => {
|
|
153
|
-
if ( rootClientId === undefined ) {
|
|
154
|
-
return rootBlocks;
|
|
155
|
-
}
|
|
156
|
-
const block = allBlocks.get( rootClientId );
|
|
157
|
-
return block ? block.innerBlocks : [];
|
|
158
|
-
} );
|
|
159
|
-
|
|
160
|
-
( select as jest.Mock ).mockReturnValue( { getBlocks } );
|
|
161
|
-
return { getBlocks };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
152
|
describe( 'getBlockPathInYdoc', () => {
|
|
165
153
|
it( 'should return path [0] for the first root block', () => {
|
|
166
154
|
const { blocks } = createFlatYDoc( 3 );
|
|
@@ -313,35 +301,30 @@ describe( 'getContainingBlockYMap', () => {
|
|
|
313
301
|
} );
|
|
314
302
|
|
|
315
303
|
describe( 'resolveBlockClientIdByPath', () => {
|
|
316
|
-
afterEach( () => {
|
|
317
|
-
jest.restoreAllMocks();
|
|
318
|
-
} );
|
|
319
|
-
|
|
320
304
|
it( 'should return null for an empty path', () => {
|
|
321
|
-
|
|
322
|
-
expect( resolveBlockClientIdByPath( [] ) ).toBeNull();
|
|
305
|
+
expect( resolveBlockClientIdByPath( [], [] ) ).toBeNull();
|
|
323
306
|
} );
|
|
324
307
|
|
|
325
308
|
it( 'should resolve a root block by single-element path', () => {
|
|
326
|
-
|
|
309
|
+
const blocks: MockBlock[] = [
|
|
327
310
|
{ clientId: 'a', name: 'core/paragraph', innerBlocks: [] },
|
|
328
311
|
{ clientId: 'b', name: 'core/heading', innerBlocks: [] },
|
|
329
|
-
]
|
|
312
|
+
];
|
|
330
313
|
|
|
331
|
-
expect( resolveBlockClientIdByPath( [ 0 ] ) ).toBe( 'a' );
|
|
332
|
-
expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBe( 'b' );
|
|
314
|
+
expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe( 'a' );
|
|
315
|
+
expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe( 'b' );
|
|
333
316
|
} );
|
|
334
317
|
|
|
335
318
|
it( 'should return null for an out-of-bounds index', () => {
|
|
336
|
-
|
|
319
|
+
const blocks: MockBlock[] = [
|
|
337
320
|
{ clientId: 'a', name: 'core/paragraph', innerBlocks: [] },
|
|
338
|
-
]
|
|
321
|
+
];
|
|
339
322
|
|
|
340
|
-
expect( resolveBlockClientIdByPath( [ 5 ] ) ).toBeNull();
|
|
323
|
+
expect( resolveBlockClientIdByPath( [ 5 ], blocks ) ).toBeNull();
|
|
341
324
|
} );
|
|
342
325
|
|
|
343
326
|
it( 'should resolve a nested inner block', () => {
|
|
344
|
-
|
|
327
|
+
const blocks: MockBlock[] = [
|
|
345
328
|
{
|
|
346
329
|
clientId: 'parent',
|
|
347
330
|
name: 'core/group',
|
|
@@ -358,13 +341,15 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
358
341
|
},
|
|
359
342
|
],
|
|
360
343
|
},
|
|
361
|
-
]
|
|
344
|
+
];
|
|
362
345
|
|
|
363
|
-
expect( resolveBlockClientIdByPath( [ 0, 1 ] ) ).toBe(
|
|
346
|
+
expect( resolveBlockClientIdByPath( [ 0, 1 ], blocks ) ).toBe(
|
|
347
|
+
'child-1'
|
|
348
|
+
);
|
|
364
349
|
} );
|
|
365
350
|
|
|
366
351
|
it( 'should return null when inner path index is out of bounds', () => {
|
|
367
|
-
|
|
352
|
+
const blocks: MockBlock[] = [
|
|
368
353
|
{
|
|
369
354
|
clientId: 'parent',
|
|
370
355
|
name: 'core/group',
|
|
@@ -376,12 +361,12 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
376
361
|
},
|
|
377
362
|
],
|
|
378
363
|
},
|
|
379
|
-
]
|
|
364
|
+
];
|
|
380
365
|
|
|
381
|
-
expect( resolveBlockClientIdByPath( [ 0, 5 ] ) ).toBeNull();
|
|
366
|
+
expect( resolveBlockClientIdByPath( [ 0, 5 ], blocks ) ).toBeNull();
|
|
382
367
|
} );
|
|
383
368
|
|
|
384
|
-
describe( 'template mode (
|
|
369
|
+
describe( 'template mode (usePostContentBlocks behavior)', () => {
|
|
385
370
|
it( 'should navigate through core/post-content in template mode', () => {
|
|
386
371
|
const postContentInnerBlocks: MockBlock[] = [
|
|
387
372
|
{
|
|
@@ -398,7 +383,7 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
398
383
|
|
|
399
384
|
// Template structure: header → post-content → footer.
|
|
400
385
|
// post-content's innerBlocks are empty in the tree (controlled
|
|
401
|
-
// inner blocks), so
|
|
386
|
+
// inner blocks), so getClientIdsTree( postContentClientId ) is used.
|
|
402
387
|
const templateBlocks: MockBlock[] = [
|
|
403
388
|
{
|
|
404
389
|
clientId: 'header',
|
|
@@ -417,13 +402,11 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
417
402
|
},
|
|
418
403
|
];
|
|
419
404
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
getBlocks.mockImplementation( ( rootClientId?: string ) => {
|
|
426
|
-
if ( rootClientId === undefined ) {
|
|
405
|
+
// Override getClientIdsTree to return post content blocks when
|
|
406
|
+
// called with the post-content clientId (mimicking controlled
|
|
407
|
+
// inner blocks behavior in useBlockSync).
|
|
408
|
+
mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
|
|
409
|
+
if ( rootClientId === '' ) {
|
|
427
410
|
return templateBlocks;
|
|
428
411
|
}
|
|
429
412
|
if ( rootClientId === 'post-content' ) {
|
|
@@ -432,13 +415,18 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
432
415
|
return [];
|
|
433
416
|
} );
|
|
434
417
|
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
expect( resolveBlockClientIdByPath( [
|
|
418
|
+
// The returned blocks should be post content blocks, not the template blocks.
|
|
419
|
+
const blocks = renderHook( () => usePostContentBlocks() ).result
|
|
420
|
+
.current;
|
|
421
|
+
expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
|
|
422
|
+
'post-para-0'
|
|
423
|
+
);
|
|
424
|
+
expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe(
|
|
425
|
+
'post-para-1'
|
|
426
|
+
);
|
|
439
427
|
} );
|
|
440
428
|
|
|
441
|
-
it( 'should call
|
|
429
|
+
it( 'should call getClientIdsTree with post-content clientId', () => {
|
|
442
430
|
const templateBlocks: MockBlock[] = [
|
|
443
431
|
{
|
|
444
432
|
clientId: 'header',
|
|
@@ -452,9 +440,8 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
452
440
|
},
|
|
453
441
|
];
|
|
454
442
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if ( rootClientId === undefined ) {
|
|
443
|
+
mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
|
|
444
|
+
if ( rootClientId === '' ) {
|
|
458
445
|
return templateBlocks;
|
|
459
446
|
}
|
|
460
447
|
if ( rootClientId === 'pc' ) {
|
|
@@ -469,10 +456,10 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
469
456
|
return [];
|
|
470
457
|
} );
|
|
471
458
|
|
|
472
|
-
|
|
459
|
+
renderHook( () => usePostContentBlocks() );
|
|
473
460
|
|
|
474
|
-
// Verify
|
|
475
|
-
expect(
|
|
461
|
+
// Verify getClientIdsTree was called with the post-content clientId.
|
|
462
|
+
expect( mockGetClientIdsTree ).toHaveBeenCalledWith( 'pc' );
|
|
476
463
|
} );
|
|
477
464
|
|
|
478
465
|
it( 'should find core/post-content nested inside template parts', () => {
|
|
@@ -500,9 +487,8 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
500
487
|
},
|
|
501
488
|
];
|
|
502
489
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if ( rootClientId === undefined ) {
|
|
490
|
+
mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
|
|
491
|
+
if ( rootClientId === '' ) {
|
|
506
492
|
return templateBlocks;
|
|
507
493
|
}
|
|
508
494
|
if ( rootClientId === 'nested-pc' ) {
|
|
@@ -511,12 +497,16 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
511
497
|
return [];
|
|
512
498
|
} );
|
|
513
499
|
|
|
514
|
-
|
|
500
|
+
const blocks = renderHook( () => usePostContentBlocks() ).result
|
|
501
|
+
.current;
|
|
502
|
+
expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
|
|
503
|
+
'deep-para'
|
|
504
|
+
);
|
|
515
505
|
} );
|
|
516
506
|
|
|
517
507
|
it( 'should use root blocks directly when no core/post-content exists', () => {
|
|
518
508
|
// No template mode — plain post editing.
|
|
519
|
-
const
|
|
509
|
+
const postContentBlocks: MockBlock[] = [
|
|
520
510
|
{
|
|
521
511
|
clientId: 'para-0',
|
|
522
512
|
name: 'core/paragraph',
|
|
@@ -529,10 +519,22 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
529
519
|
},
|
|
530
520
|
];
|
|
531
521
|
|
|
532
|
-
|
|
522
|
+
mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
|
|
523
|
+
if ( rootClientId === '' ) {
|
|
524
|
+
return postContentBlocks;
|
|
525
|
+
}
|
|
526
|
+
return [];
|
|
527
|
+
} );
|
|
533
528
|
|
|
534
|
-
|
|
535
|
-
|
|
529
|
+
const blocks = renderHook( () => usePostContentBlocks() ).result
|
|
530
|
+
.current;
|
|
531
|
+
|
|
532
|
+
expect( resolveBlockClientIdByPath( [ 0 ], blocks ) ).toBe(
|
|
533
|
+
'para-0'
|
|
534
|
+
);
|
|
535
|
+
expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBe(
|
|
536
|
+
'para-1'
|
|
537
|
+
);
|
|
536
538
|
} );
|
|
537
539
|
|
|
538
540
|
it( 'should return null for invalid path in template mode', () => {
|
|
@@ -549,9 +551,8 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
549
551
|
},
|
|
550
552
|
];
|
|
551
553
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if ( rootClientId === undefined ) {
|
|
554
|
+
mockGetClientIdsTree = jest.fn( ( rootClientId: string = '' ) => {
|
|
555
|
+
if ( rootClientId === '' ) {
|
|
555
556
|
return templateBlocks;
|
|
556
557
|
}
|
|
557
558
|
if ( rootClientId === 'pc' ) {
|
|
@@ -567,8 +568,11 @@ describe( 'resolveBlockClientIdByPath', () => {
|
|
|
567
568
|
return [];
|
|
568
569
|
} );
|
|
569
570
|
|
|
571
|
+
const blocks = renderHook( () => usePostContentBlocks() ).result
|
|
572
|
+
.current;
|
|
573
|
+
|
|
570
574
|
// Index 1 is out of bounds for the post content blocks.
|
|
571
|
-
expect( resolveBlockClientIdByPath( [ 1 ] ) ).toBeNull();
|
|
575
|
+
expect( resolveBlockClientIdByPath( [ 1 ], blocks ) ).toBeNull();
|
|
572
576
|
} );
|
|
573
577
|
} );
|
|
574
578
|
} );
|