UNPKG

@wordpress/editor

Version:
1,112 lines (1,044 loc) 30.2 kB
/** * WordPress dependencies */ import { createBlock, registerBlockType, unregisterBlockType, getBlockType, serialize, } from '@wordpress/blocks'; import { RichTextData } from '@wordpress/rich-text'; import * as paragraphBlock from '@wordpress/block-library/src/paragraph'; import * as groupBlock from '@wordpress/block-library/src/group'; /** * Internal dependencies */ import { diffRevisionContent } from '../block-diff'; import { registerDiffFormatTypes, unregisterDiffFormatTypes, } from '../diff-format-types'; /** * Convert blocks to a normalized format for comparison. * Converts RichTextData to HTML strings, similar to E2E test utils. * Always includes __revisionDiffStatus so we can verify its absence. * * @param {Array} blocks The blocks to normalize. * @return {Array} Normalized blocks with RichTextData converted to strings. */ function normalizeBlockTree( blocks ) { return blocks.map( ( block ) => { const attributes = Object.fromEntries( Object.entries( block.attributes ).map( ( [ key, value ] ) => { if ( value instanceof RichTextData ) { return [ key, value.toHTMLString() ]; } return [ key, value ]; } ) ); return { name: block.name, attributes: { ...attributes, __revisionDiffStatus: block.__revisionDiffStatus, }, innerBlocks: normalizeBlockTree( block.innerBlocks ), }; } ); } describe( 'diffRevisionContent', () => { beforeAll( () => { // Register actual core blocks for testing. if ( ! getBlockType( 'core/paragraph' ) ) { registerBlockType( { name: paragraphBlock.name, ...paragraphBlock.metadata }, paragraphBlock.settings ); } if ( ! getBlockType( 'core/group' ) ) { registerBlockType( { name: groupBlock.name, ...groupBlock.metadata }, groupBlock.settings ); } registerDiffFormatTypes(); } ); afterAll( () => { if ( getBlockType( 'core/paragraph' ) ) { unregisterBlockType( 'core/paragraph' ); } if ( getBlockType( 'core/group' ) ) { unregisterBlockType( 'core/group' ); } unregisterDiffFormatTypes(); } ); it( 'marks all blocks as added when no previous content', () => { const current = serialize( [ createBlock( 'core/paragraph', { content: 'Hello' } ), ] ); const blocks = diffRevisionContent( current, '' ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello', __revisionDiffStatus: { status: 'added' }, }, }, ] ); } ); it( 'marks all blocks as removed when no current content', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello' } ), ] ); const blocks = diffRevisionContent( '', previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello', __revisionDiffStatus: { status: 'removed' }, }, }, ] ); } ); it( 'leaves unchanged blocks unmarked', () => { const content = serialize( [ createBlock( 'core/paragraph', { content: 'Hello' } ), ] ); const blocks = diffRevisionContent( content, content ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'detects changed paragraph content as modified', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello' } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'World' } ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'uses LCS so only changed blocks are marked', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'NEW' } ), createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'NEW', __revisionDiffStatus: { status: 'added' }, }, }, { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'handles two blocks added above a slightly modified paragraph', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'This is some existing content', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'First new block' } ), createBlock( 'core/paragraph', { content: 'Second new block' } ), createBlock( 'core/paragraph', { content: 'This is some modified content', } ), ] ); const blocks = diffRevisionContent( current, previous ); const normalized = normalizeBlockTree( blocks ); // Post-LCS pairing detects similar blocks and marks them as modified. // The removed block is filtered out, modified block shows inline diff. expect( normalized ).toHaveLength( 3 ); expect( normalized ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'First new block', __revisionDiffStatus: { status: 'added' }, }, }, { name: 'core/paragraph', attributes: { content: 'Second new block', __revisionDiffStatus: { status: 'added' }, }, }, { name: 'core/paragraph', attributes: { // Inline diff: "existing""modified" content: 'This is some <del title="Removed" class="revision-diff-removed">existing</del><ins title="Added" class="revision-diff-added">modified</ins> content', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'handles inner block changes without marking parent', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: { status: 'added' }, }, }, ], }, ] ); } ); it( 'handles removed inner blocks', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: { status: 'removed' }, }, }, ], }, ] ); } ); it( 'returns empty array for empty content', () => { const blocks = diffRevisionContent( '', '' ); expect( blocks ).toEqual( [] ); } ); it( 'handles two blocks that swapped positions', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'First block content' } ), createBlock( 'core/paragraph', { content: 'Second block content', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Second block content', } ), createBlock( 'core/paragraph', { content: 'First block content' } ), ] ); const blocks = diffRevisionContent( current, previous ); // LCS matches one block ("First block content" at prev[0] -> curr[1]). // The other block appears as removed + added (showing the reorder). // We intentionally don't pair identical blocks as "modified" since // there's no actual content change - just a position change. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Second block content', __revisionDiffStatus: { status: 'added' }, }, }, { name: 'core/paragraph', attributes: { content: 'First block content', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'Second block content', __revisionDiffStatus: { status: 'removed' }, }, }, ] ); } ); it( 'pairs blocks as modified when attrs differ but content is identical', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Same content' } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Same content', className: 'new-class', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Content is identical but attrs changed - should be marked as modified. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Same content', className: 'new-class', __revisionDiffStatus: { status: 'modified', changedAttributes: { className: [ { added: true, value: 'new-class', }, ], }, }, }, }, ] ); } ); it( 'handles block move with a tiny change', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'First block content' } ), createBlock( 'core/paragraph', { content: 'Second block content', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Second block content modified', } ), createBlock( 'core/paragraph', { content: 'First block content' } ), ] ); const blocks = diffRevisionContent( current, previous ); // The moved+modified block is correctly paired and shows inline diff. // The unchanged block remains unmarked. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Second block content<ins title="Added" class="revision-diff-added"> modified</ins>', __revisionDiffStatus: { status: 'modified', }, }, }, { name: 'core/paragraph', attributes: { content: 'First block content', __revisionDiffStatus: undefined, }, }, ] ); } ); describe( 'inner blocks', () => { it( 'handles deeply nested inner blocks', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Deep' } ), ] ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Deep' } ), createBlock( 'core/paragraph', { content: 'New' } ), ] ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'Deep', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'New', __revisionDiffStatus: { status: 'added', }, }, }, ], }, ], }, ] ); } ); it( 'does not mark inner blocks when container is added (parent styling is sufficient)', () => { const previous = ''; const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: { status: 'added' }, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: undefined, }, }, ], }, ] ); } ); it( 'does not mark inner blocks when container is removed (parent styling is sufficient)', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), ] ), ] ); const current = ''; const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: { status: 'removed' }, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: undefined, }, }, ], }, ] ); } ); it( 'uses LCS for inner blocks so only changed ones are marked', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), createBlock( 'core/paragraph', { content: 'C' } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'NEW' } ), createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), createBlock( 'core/paragraph', { content: 'C' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'NEW', __revisionDiffStatus: { status: 'added' }, }, }, { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'B', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'C', __revisionDiffStatus: undefined, }, }, ], }, ] ); } ); it( 'handles changed inner block content as modified', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Original' } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Modified' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { __revisionDiffStatus: { status: 'modified', }, }, }, ], }, ] ); } ); it( 'handles multiple inner block changes at once', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'B' } ), createBlock( 'core/paragraph', { content: 'C' } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'A' } ), createBlock( 'core/paragraph', { content: 'D' } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); // Post-LCS pairing matches B with D (same block type, high HTML similarity). // C remains removed since it has no matching added block. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'A', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'C', __revisionDiffStatus: { status: 'removed' }, }, }, { name: 'core/paragraph', attributes: { // B→D modification with inline diff content: '<del title="Removed" class="revision-diff-removed">B</del><ins title="Added" class="revision-diff-added">D</ins>', __revisionDiffStatus: { status: 'modified', }, }, }, ], }, ] ); } ); it( 'does not pair blocks with completely different content', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'First paragraph stays the same', } ), createBlock( 'core/paragraph', { content: 'The quick brown fox jumps over the lazy dog near the riverbank', } ), createBlock( 'core/paragraph', { content: 'Third paragraph also removed from this post', } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'First paragraph stays the same', } ), createBlock( 'core/paragraph', { content: 'Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod', } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); // With word-based similarity, completely different sentences are NOT paired. // They appear as separate removed + added blocks. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: 'First paragraph stays the same', __revisionDiffStatus: undefined, }, }, { name: 'core/paragraph', attributes: { content: 'The quick brown fox jumps over the lazy dog near the riverbank', __revisionDiffStatus: { status: 'removed' }, }, }, { name: 'core/paragraph', attributes: { content: 'Third paragraph also removed from this post', __revisionDiffStatus: { status: 'removed' }, }, }, { name: 'core/paragraph', attributes: { content: 'Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod', __revisionDiffStatus: { status: 'added' }, }, }, ], }, ] ); } ); } ); describe( 'rich text formatting', () => { it( 'detects unchanged paragraph with bold formatting', () => { const content = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>', } ), ] ); const blocks = diffRevisionContent( content, content ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello <strong>world</strong>', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'detects added bold formatting as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello world' } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Format-only change: only "world" is marked (where bold was added). // "Hello " is not marked since its formatting didn't change. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello <strong><span title="1 format added" class="revision-diff-format-added">world</span></strong>', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects changed text within bold formatting as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>everyone</strong>', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Word-level diff: "world" changed to "everyone" within bold formatting. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello <strong><del title="Removed" class="revision-diff-removed">world</del><ins title="Added" class="revision-diff-added">everyone</ins></strong>', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects unchanged paragraph with link', () => { const content = serialize( [ createBlock( 'core/paragraph', { content: 'Visit <a href="https://example.com">our site</a> today', } ), ] ); const blocks = diffRevisionContent( content, content ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Visit <a href="https://example.com">our site</a> today', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'detects changed link URL as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Visit <a href="https://old-site.com">our site</a> today', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Visit <a href="https://new-site.com">our site</a> today', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Format-only change: only the link text is marked (where URL changed). // "Visit " and " today" are not marked. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Visit <a href="https://new-site.com"><span title="1 format changed" class="revision-diff-format-changed">our site</span></a> today', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects changed link text as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Visit <a href="https://example.com">our site</a> today', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Visit <a href="https://example.com">the website</a> today', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Word-level diff: "our site" changed to "the website" within link. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Visit <a href="https://example.com"><del title="Removed" class="revision-diff-removed">our</del><ins title="Added" class="revision-diff-added">the</ins> <del title="Removed" class="revision-diff-removed">site</del><ins title="Added" class="revision-diff-added">website</ins></a> today', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects unchanged paragraph with mixed formatting', () => { const content = serialize( [ createBlock( 'core/paragraph', { content: 'This has <strong>bold</strong>, <em>italic</em>, and <a href="https://example.com">links</a>', } ), ] ); const blocks = diffRevisionContent( content, content ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'This has <strong>bold</strong>, <em>italic</em>, and <a href="https://example.com">links</a>', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'detects removed formatting as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: '<strong>Bold</strong> and <em>italic</em> text', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Bold and italic text', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Format-only change: only "Bold" and "italic" are marked (where formatting was removed). // " and " and " text" are not marked since they never had formatting. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: '<span title="1 format removed" class="revision-diff-format-removed">Bold</span> and <span title="1 format removed" class="revision-diff-format-removed">italic</span> text', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects bold replaced by italic as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <em>world</em>', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Mixed change: bold removed AND italic added = "changed" type (yellow outline). expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Hello <em><span title="1 format added, 1 format removed" class="revision-diff-format-changed">world</span></em>', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'detects unchanged paragraph with inline code', () => { const content = serialize( [ createBlock( 'core/paragraph', { content: 'Use the <code>console.log()</code> function', } ), ] ); const blocks = diffRevisionContent( content, content ); expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: 'Use the <code>console.log()</code> function', __revisionDiffStatus: undefined, }, }, ] ); } ); it( 'detects text change outside formatting as modification', () => { const previous = serialize( [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>!', } ), ] ); const current = serialize( [ createBlock( 'core/paragraph', { content: 'Goodbye <strong>world</strong>!', } ), ] ); const blocks = diffRevisionContent( current, previous ); // Word-level diff: "Hello" changed to "Goodbye" outside the bold formatting. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/paragraph', attributes: { content: '<del title="Removed" class="revision-diff-removed">Hello</del><ins title="Added" class="revision-diff-added">Goodbye</ins> <strong>world</strong>!', __revisionDiffStatus: { status: 'modified', }, }, }, ] ); } ); it( 'applies rich text diff to nested block content', () => { const previous = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Hello <strong>world</strong>', } ), ] ), ] ); const current = serialize( [ createBlock( 'core/group', {}, [ createBlock( 'core/paragraph', { content: 'Goodbye <strong>everyone</strong>', } ), ] ), ] ); const blocks = diffRevisionContent( current, previous ); // Word-level diff applied to nested paragraph content. expect( normalizeBlockTree( blocks ) ).toMatchObject( [ { name: 'core/group', attributes: { __revisionDiffStatus: undefined, }, innerBlocks: [ { name: 'core/paragraph', attributes: { content: '<del title="Removed" class="revision-diff-removed">Hello</del><ins title="Added" class="revision-diff-added">Goodbye</ins> <strong><del title="Removed" class="revision-diff-removed">world</del><ins title="Added" class="revision-diff-added">everyone</ins></strong>', __revisionDiffStatus: { status: 'modified', }, }, }, ], }, ] ); } ); } ); } );