Skip to content

Commit bc99fc2

Browse files
authored
Real-time collaboration: Ensure block attribute is Y.Text (#72731)
1 parent 6fe78b5 commit bc99fc2

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

packages/core-data/src/utils/crdt-blocks.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,16 +294,17 @@ export function mergeCrdtBlocks(
294294
if (
295295
isRichText &&
296296
'string' === typeof attributeValue &&
297-
currentAttributes.has( attributeName )
297+
currentAttributes.has( attributeName ) &&
298+
currentAttributes.get(
299+
attributeName
300+
) instanceof Y.Text
298301
) {
299302
// Rich text values are stored as persistent Y.Text instances.
300303
// Update the value with a delta in place.
301-
const blockYText = currentAttributes.get(
302-
attributeName
303-
) as Y.Text;
304-
305304
mergeRichTextUpdate(
306-
blockYText,
305+
currentAttributes.get(
306+
attributeName
307+
) as Y.Text,
307308
attributeValue,
308309
cursorPosition
309310
);

packages/core-data/src/utils/test/crdt-blocks.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ jest.mock( 'uuid', () => ( {
1616
v4: jest.fn( () => 'mocked-uuid-' + Math.random() ),
1717
} ) );
1818

19+
/**
20+
* Mock @wordpress/blocks module
21+
*/
22+
jest.mock( '@wordpress/blocks', () => ( {
23+
getBlockTypes: jest.fn( () => [
24+
{
25+
name: 'core/paragraph',
26+
attributes: { content: { type: 'rich-text' } },
27+
},
28+
] ),
29+
} ) );
30+
1931
/**
2032
* Internal dependencies
2133
*/
@@ -269,9 +281,50 @@ describe( 'crdt-blocks', () => {
269281
const contentAttr = (
270282
block.get( 'attributes' ) as YBlockAttributes
271283
).get( 'content' ) as Y.Text;
284+
expect( contentAttr ).toBeInstanceOf( Y.Text );
272285
expect( contentAttr.toString() ).toBe( 'Rich text content' );
273286
} );
274287

288+
it( 'creates Y.Text for rich-text attributes even when the block name changes', () => {
289+
const blocks: Block[] = [
290+
{
291+
name: 'core/freeform',
292+
attributes: { content: 'Freeform text' },
293+
innerBlocks: [],
294+
},
295+
];
296+
297+
mergeCrdtBlocks( yblocks, blocks, null );
298+
299+
const block = yblocks.get( 0 );
300+
const contentAttr = (
301+
block.get( 'attributes' ) as YBlockAttributes
302+
).get( 'content' );
303+
expect( block.get( 'name' ) ).toBe( 'core/freeform' );
304+
expect( typeof contentAttr ).toBe( 'string' );
305+
expect( contentAttr ).toBe( 'Freeform text' );
306+
307+
const updatedBlocks: Block[] = [
308+
{
309+
name: 'core/paragraph',
310+
attributes: { content: 'Updated text' },
311+
innerBlocks: [],
312+
},
313+
];
314+
315+
mergeCrdtBlocks( yblocks, updatedBlocks, null );
316+
317+
expect( yblocks.length ).toBe( 1 );
318+
319+
const updatedBlock = yblocks.get( 0 );
320+
const updatedContentAttr = (
321+
updatedBlock.get( 'attributes' ) as YBlockAttributes
322+
).get( 'content' ) as Y.Text;
323+
expect( updatedBlock.get( 'name' ) ).toBe( 'core/paragraph' );
324+
expect( updatedContentAttr ).toBeInstanceOf( Y.Text );
325+
expect( updatedContentAttr.toString() ).toBe( 'Updated text' );
326+
} );
327+
275328
it( 'removes duplicate clientIds', () => {
276329
const blocksWithDuplicateIds: Block[] = [
277330
{

0 commit comments

Comments
 (0)