@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
193 lines (169 loc) • 4.42 kB
JavaScript
import { act, render, waitFor } from '@testing-library/react';
import {
afterEach,
beforeEach,
describe,
expect,
it,
jest,
} from '@jest/globals';
import {
getBlockTypes,
registerBlockType,
unregisterBlockType,
} from '@wordpress/blocks';
import { RichText } from '@wordpress/block-editor';
import { createRegistry, RegistryProvider } from '@wordpress/data';
import { Y } from '@wordpress/sync';
/**
* Mock sync manager accessor.
*/
jest.mock( '../sync', () => ( {
...jest.requireActual( '../sync' ),
getSyncManager: jest.fn(),
LOCAL_EDITOR_ORIGIN: 'local-editor',
} ) );
import { store as coreDataStore } from '../index';
import { CRDT_RECORD_MAP_KEY, getSyncManager } from '../sync';
import useEntityBlockEditor from '../hooks/use-entity-block-editor';
import { applyPostChangesToCRDTDoc } from '../utils/crdt';
import { getRootMap } from '../utils/crdt-utils';
const mockGetSyncManager = jest.mocked( getSyncManager );
const postTypeConfig = {
kind: 'postType',
name: 'post',
baseURL: '/wp/v2/posts',
transientEdits: { blocks: true, selection: true },
mergedEdits: { meta: true },
rawAttributes: [ 'title', 'excerpt', 'content' ],
syncConfig: {},
};
const postTypeEntity = {
slug: 'post',
rest_base: 'posts',
labels: {
item_updated: 'Updated Post',
item_published: 'Post published',
item_reverted_to_draft: 'Post reverted to draft.',
},
};
const OLD_HTML = '<em>italic</em><em>italic</em>';
const NEW_HTML = '<em>italic</em>beta';
const TEXT_OFFSET = 10;
const SYNCED_PROPERTIES = new Set( [ 'blocks' ] );
function createRegistryWithStores() {
const registry = createRegistry();
registry.register( coreDataStore );
registry.dispatch( coreDataStore ).addEntities( [ postTypeConfig ] );
registry
.dispatch( coreDataStore )
.receiveEntityRecords( 'root', 'postType', [ postTypeEntity ] );
registry
.dispatch( coreDataStore )
.receiveEntityRecords( 'postType', 'post', [
{
id: 1,
type: 'post',
content: {
raw: `<!-- wp:paragraph --><p>${ OLD_HTML }</p><!-- /wp:paragraph -->`,
rendered: `<p>${ OLD_HTML }</p>`,
},
meta: {},
},
] );
return registry;
}
function readFirstBlockContentFromDoc( doc ) {
const ymap = getRootMap( doc, CRDT_RECORD_MAP_KEY );
const yblocks = ymap.get( 'blocks' );
const attrs = yblocks.get( 0 ).get( 'attributes' );
return attrs.get( 'content' ).toString();
}
describe( 'useEntityBlockEditor RTC rich-text offset-space bug', () => {
let crdtDoc;
beforeEach( () => {
crdtDoc = new Y.Doc();
registerBlockType( 'core/paragraph', {
apiVersion: 3,
title: 'Paragraph',
category: 'text',
attributes: {
content: {
type: 'rich-text',
source: 'rich-text',
selector: 'p',
role: 'content',
},
},
edit: () => null,
save: ( { attributes } ) => (
<p>
<RichText.Content value={ attributes.content } />
</p>
),
} );
mockGetSyncManager.mockReturnValue( {
update: jest.fn( ( _objectType, _objectId, changes ) => {
applyPostChangesToCRDTDoc(
crdtDoc,
changes,
SYNCED_PROPERTIES
);
} ),
} );
} );
afterEach( () => {
crdtDoc.destroy();
mockGetSyncManager.mockReset();
if (
getBlockTypes().some( ( block ) => block.name === 'core/paragraph' )
) {
unregisterBlockType( 'core/paragraph' );
}
} );
it( 'preserves formatted paragraph content when onInput forwards blocks plus selection', async () => {
const registry = createRegistryWithStores();
let blocks;
let onInput;
const TestComponent = () => {
[ blocks, onInput ] = useEntityBlockEditor( 'postType', 'post', {
id: 1,
} );
return <div />;
};
render(
<RegistryProvider value={ registry }>
<TestComponent />
</RegistryProvider>
);
await waitFor( () => expect( blocks ).toHaveLength( 1 ) );
const selection = {
selectionStart: {
clientId: blocks[ 0 ].clientId,
attributeKey: 'content',
offset: TEXT_OFFSET,
},
selectionEnd: {
clientId: blocks[ 0 ].clientId,
attributeKey: 'content',
offset: TEXT_OFFSET,
},
};
act( () => {
onInput( blocks, { selection } );
} );
const nextBlocks = [
{
...blocks[ 0 ],
attributes: {
...blocks[ 0 ].attributes,
content: NEW_HTML,
},
},
];
act( () => {
onInput( nextBlocks, { selection } );
} );
expect( readFirstBlockContentFromDoc( crdtDoc ) ).toBe( NEW_HTML );
} );
} );