@wordpress/block-editor
Version:
2,329 lines (2,153 loc) • 105 kB
JavaScript
/**
* WordPress dependencies
*/
import {
registerBlockType,
unregisterBlockType,
setFreeformContentHandlerName,
} from '@wordpress/blocks';
import { RawHTML } from '@wordpress/element';
import { symbol } from '@wordpress/icons';
import { select, dispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import * as selectors from '../selectors';
import { store } from '../';
import { lock } from '../../lock-unlock';
const {
getBlockName,
getBlock,
getBlocks,
getBlockParents,
getBlockParentsByBlockName,
getBlockCount,
getClientIdsWithDescendants,
getClientIdsOfDescendants,
hasSelectedBlock,
getSelectedBlock,
getSelectedBlockClientId,
getBlockRootClientId,
getBlockHierarchyRootClientId,
getGlobalBlockCount,
getSelectedBlockClientIds,
getMultiSelectedBlockClientIds,
getMultiSelectedBlocks,
getMultiSelectedBlocksStartClientId,
getMultiSelectedBlocksEndClientId,
getBlockOrder,
getBlockIndex,
getPreviousBlockClientId,
getNextBlockClientId,
isBlockSelected,
hasSelectedInnerBlock,
isBlockWithinSelection,
hasMultiSelection,
isBlockMultiSelected,
isFirstMultiSelectedBlock,
getBlockMode,
isTyping,
isDraggingBlocks,
getDraggedBlockClientIds,
isBlockBeingDragged,
isAncestorBeingDragged,
getBlockInsertionPoint,
isBlockInsertionPointVisible,
isSelectionEnabled,
canInsertBlockType,
canInsertBlocks,
getBlockTransformItems,
isValidTemplate,
getTemplate,
getTemplateLock,
getBlockListSettings,
__experimentalGetBlockListSettingsForBlocks,
__experimentalGetLastBlockAttributeChanges,
getLowestCommonAncestorWithSelectedBlock,
__experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames,
__unstableGetClientIdWithClientIdsTree,
__unstableGetClientIdsTree,
wasBlockJustInserted,
getBlocksByName,
getBlockEditingMode,
} = selectors;
describe( 'selectors', () => {
let cachedSelectors;
beforeAll( () => {
cachedSelectors = Object.entries( selectors )
.filter( ( [ , selector ] ) => selector.clear )
.map( ( [ , selector ] ) => selector );
} );
beforeEach( () => {
registerBlockType( 'core/block', {
save: () => null,
category: 'reusable',
title: 'Reusable Block Stub',
supports: {
inserter: false,
},
} );
registerBlockType( 'core/test-block-a', {
save: ( props ) => props.attributes.text,
category: 'design',
title: 'Test Block A',
icon: 'test',
keywords: [ 'testing' ],
} );
registerBlockType( 'core/test-block-b', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block B',
icon: 'test',
keywords: [ 'testing' ],
supports: {
multiple: false,
},
} );
registerBlockType( 'core/test-block-c', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block C',
icon: 'test',
keywords: [ 'testing' ],
parent: [ 'core/test-block-b' ],
} );
registerBlockType( 'core/freeform', {
save: ( props ) => <RawHTML>{ props.attributes.content }</RawHTML>,
category: 'text',
title: 'Test Freeform Content Handler',
icon: 'test',
attributes: {
content: {
type: 'string',
},
},
} );
registerBlockType( 'core/post-content-child', {
save: () => null,
category: 'text',
title: 'Test Block Post Content Child',
icon: 'test',
keywords: [ 'testing' ],
parent: [ 'core/post-content' ],
} );
registerBlockType( 'core/test-block-ancestor', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block required as ancestor',
icon: 'test',
keywords: [ 'testing' ],
} );
registerBlockType( 'core/test-block-parent', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block required as parent',
icon: 'test',
keywords: [ 'testing' ],
} );
registerBlockType( 'core/test-block-requires-ancestor', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block that requires ancestor',
icon: 'test',
keywords: [ 'testing' ],
ancestor: [ 'core/test-block-ancestor' ],
} );
registerBlockType( 'core/test-block-requires-ancestor-parent', {
save: ( props ) => props.attributes.text,
category: 'text',
title: 'Test Block that requires both ancestor and parent',
icon: 'test',
keywords: [ 'testing' ],
parent: [ 'core/test-block-parent' ],
ancestor: [ 'core/test-block-ancestor' ],
} );
setFreeformContentHandlerName( 'core/freeform' );
cachedSelectors.forEach( ( { clear } ) => clear() );
} );
afterEach( () => {
unregisterBlockType( 'core/block' );
unregisterBlockType( 'core/test-block-a' );
unregisterBlockType( 'core/test-block-b' );
unregisterBlockType( 'core/test-block-c' );
unregisterBlockType( 'core/freeform' );
unregisterBlockType( 'core/post-content-child' );
unregisterBlockType( 'core/test-block-ancestor' );
unregisterBlockType( 'core/test-block-parent' );
unregisterBlockType( 'core/test-block-requires-ancestor' );
unregisterBlockType( 'core/test-block-requires-ancestor-parent' );
setFreeformContentHandlerName( undefined );
} );
describe( 'getBlockName', () => {
it( 'returns null if no block by clientId', () => {
const state = {
blocks: {
byClientId: new Map(),
attributes: {},
order: new Map(),
parents: new Map(),
},
};
const name = getBlockName(
state,
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
);
expect( name ).toBe( null );
} );
it( 'returns block name', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {
clientId:
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
name: 'core/paragraph',
},
} )
),
attributes: new Map(
Object.entries( {
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {},
} )
),
order: new Map(
Object.entries( {
'': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ],
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [],
} )
),
parents: new Map(
Object.entries( {
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': '',
} )
),
},
};
const name = getBlockName(
state,
'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1'
);
expect( name ).toBe( 'core/paragraph' );
} );
} );
describe( 'getBlock', () => {
it( 'should return the block', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123' ],
123: [],
} )
),
parents: new Map(
Object.entries( {
123: '',
} )
),
tree: new Map(
Object.entries( {
123: {
clientId: '123',
name: 'core/paragraph',
attributes: {},
innerBlocks: [],
},
} )
),
controlledInnerBlocks: {},
},
};
expect( getBlock( state, '123' ) ).toBe(
state.blocks.tree.get( '123' )
);
} );
it( 'should return null if the block is not present in state', () => {
const state = {
blocks: {
byClientId: new Map(),
attributes: new Map(),
order: new Map(),
parents: new Map(),
tree: new Map(
Object.entries( {
123: {
clientId: '123',
name: 'core/paragraph',
attributes: {},
innerBlocks: [],
},
} )
),
controlledInnerBlocks: {},
},
};
expect( getBlock( state, '123' ) ).toBe( null );
} );
} );
describe( 'getBlocks', () => {
it( 'should return the ordered blocks', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: { clientId: '23', name: 'core/heading' },
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
23: {},
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
23: '',
} )
),
tree: new Map(
Object.entries( {
'': {
innerBlocks: [
{
clientId: '123',
name: 'core/paragraph',
attributes: {},
innerBlocks: [],
},
{
clientId: '23',
name: 'core/heading',
attributes: {},
innerBlocks: [],
},
],
},
123: {},
23: {},
} )
),
controlledInnerBlocks: {},
},
};
expect( getBlocks( state ) ).toBe(
state.blocks.tree.get( '' ).innerBlocks
);
} );
} );
describe( 'getBlockParents', () => {
it( 'should return parent blocks', () => {
const state = {
blocks: {
parents: new Map(
Object.entries( {
'client-id-01': '',
'client-id-02': 'client-id-01',
'client-id-03': 'client-id-02',
'client-id-04': 'client-id-03',
} )
),
byClientId: new Map(
Object.entries( {
'client-id-01': {
clientId: 'client-id-01',
name: 'core/columns',
},
'client-id-02': {
clientId: 'client-id-02',
name: 'core/navigation',
},
'client-id-03': {
clientId: 'client-id-03',
name: 'core/navigation-link',
},
'client-id-04': {
clientId: 'client-id-04',
name: 'core/paragraph',
},
} )
),
cache: {
'client-id-01': {},
'client-id-02': {},
'client-id-03': {},
'client-id-04': {},
},
controlledInnerBlocks: {},
},
};
expect( getBlockParents( state, 'client-id-04' ) ).toEqual( [
'client-id-01',
'client-id-02',
'client-id-03',
] );
expect( getBlockParents( state, 'client-id-0' ) ).toEqual( [] );
} );
} );
describe( 'getBlockParentsByBlockName', () => {
const state = {
blocks: {
parents: new Map(
Object.entries( {
'client-id-01': '',
'client-id-02': 'client-id-01',
'client-id-03': 'client-id-02',
'client-id-04': 'client-id-03',
'client-id-05': 'client-id-04',
} )
),
byClientId: new Map(
Object.entries( {
'client-id-01': {
clientId: 'client-id-01',
name: 'core/navigation',
},
'client-id-02': {
clientId: 'client-id-02',
name: 'core/columns',
},
'client-id-03': {
clientId: 'client-id-03',
name: 'core/navigation',
},
'client-id-04': {
clientId: 'client-id-04',
name: 'core/navigation-link',
},
'client-id-05': {
clientId: 'client-id-05',
name: 'core/navigation-link',
},
} )
),
cache: {
'client-id-01': {},
'client-id-02': {},
'client-id-03': {},
'client-id-04': {},
'client-id-05': {},
},
controlledInnerBlocks: {},
},
};
it( 'should return parent blocks', () => {
expect(
getBlockParentsByBlockName(
state,
'client-id-05',
'core/navigation'
)
).toEqual( [ 'client-id-01', 'client-id-03' ] );
expect(
getBlockParentsByBlockName(
state,
'client-id-05',
'core/columns'
)
).toEqual( [ 'client-id-02' ] );
expect(
getBlockParentsByBlockName(
state,
'client-id-5',
'core/unknown-block'
)
).toEqual( [] );
} );
it( 'Should optionally accept an array of parent types and return parents of multiple types', () => {
expect(
getBlockParentsByBlockName( state, 'client-id-05', [
'core/navigation',
] )
).toEqual( [ 'client-id-01', 'client-id-03' ] );
expect(
getBlockParentsByBlockName( state, 'client-id-05', [
'core/columns',
'core/navigation',
] )
).toEqual( [ 'client-id-01', 'client-id-02', 'client-id-03' ] );
} );
} );
describe( 'getClientIdsOfDescendants', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
'uuid-2': {
clientId: 'uuid-2',
name: 'core/image',
},
'uuid-4': {
clientId: 'uuid-4',
name: 'core/paragraph',
},
'uuid-6': {
clientId: 'uuid-6',
name: 'core/paragraph',
},
'uuid-8': {
clientId: 'uuid-8',
name: 'core/block',
},
'uuid-10': {
clientId: 'uuid-10',
name: 'core/columns',
},
'uuid-12': {
clientId: 'uuid-12',
name: 'core/column',
},
'uuid-14': {
clientId: 'uuid-14',
name: 'core/column',
},
'uuid-16': {
clientId: 'uuid-16',
name: 'core/quote',
},
'uuid-18': {
clientId: 'uuid-18',
name: 'core/block',
},
'uuid-20': {
clientId: 'uuid-20',
name: 'core/gallery',
},
'uuid-22': {
clientId: 'uuid-22',
name: 'core/block',
},
'uuid-24': {
clientId: 'uuid-24',
name: 'core/columns',
},
'uuid-26': {
clientId: 'uuid-26',
name: 'core/column',
},
'uuid-28': {
clientId: 'uuid-28',
name: 'core/column',
},
'uuid-30': {
clientId: 'uuid-30',
name: 'core/paragraph',
},
} )
),
attributes: new Map(
Object.entries( {
'uuid-2': {},
'uuid-4': {},
'uuid-6': {},
'uuid-8': {},
'uuid-10': {},
'uuid-12': {},
'uuid-14': {},
'uuid-16': {},
'uuid-18': {},
'uuid-20': {},
'uuid-22': {},
'uuid-24': {},
'uuid-26': {},
'uuid-28': {},
'uuid-30': {},
} )
),
order: new Map(
Object.entries( {
'': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ],
'uuid-2': [],
'uuid-4': [],
'uuid-6': [],
'uuid-8': [],
'uuid-10': [ 'uuid-12', 'uuid-14' ],
'uuid-12': [ 'uuid-16' ],
'uuid-14': [ 'uuid-18' ],
'uuid-16': [],
'uuid-18': [ 'uuid-24' ],
'uuid-20': [],
'uuid-22': [],
'uuid-24': [ 'uuid-26', 'uuid-28' ],
'uuid-26': [],
'uuid-28': [ 'uuid-30' ],
} )
),
parents: new Map(
Object.entries( {
'uuid-6': '',
'uuid-8': '',
'uuid-10': '',
'uuid-22': '',
'uuid-12': 'uuid-10',
'uuid-14': 'uuid-10',
'uuid-16': 'uuid-12',
'uuid-18': 'uuid-14',
'uuid-24': 'uuid-18',
'uuid-26': 'uuid-24',
'uuid-28': 'uuid-24',
'uuid-30': 'uuid-28',
} )
),
controlledInnerBlocks: {},
},
};
it( 'should return the ids of any descendants in sequential order, given an array of clientIds', () => {
expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual(
[
'uuid-12',
'uuid-16',
'uuid-14',
'uuid-18',
'uuid-24',
'uuid-26',
'uuid-28',
'uuid-30',
]
);
} );
it( 'should return same value when called with same state and argument', () => {
expect( getClientIdsOfDescendants( state, 'uuid-10' ) ).toBe(
getClientIdsOfDescendants( state, 'uuid-10' )
);
} );
} );
describe( 'getClientIdsWithDescendants', () => {
it( 'should return the ids for top-level blocks and their descendants of any depth (for nested blocks) in sequential order.', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
'uuid-2': {
clientId: 'uuid-2',
name: 'core/image',
},
'uuid-4': {
clientId: 'uuid-4',
name: 'core/paragraph',
},
'uuid-6': {
clientId: 'uuid-6',
name: 'core/paragraph',
},
'uuid-8': {
clientId: 'uuid-8',
name: 'core/block',
},
'uuid-10': {
clientId: 'uuid-10',
name: 'core/columns',
},
'uuid-12': {
clientId: 'uuid-12',
name: 'core/column',
},
'uuid-14': {
clientId: 'uuid-14',
name: 'core/column',
},
'uuid-16': {
clientId: 'uuid-16',
name: 'core/quote',
},
'uuid-18': {
clientId: 'uuid-18',
name: 'core/block',
},
'uuid-20': {
clientId: 'uuid-20',
name: 'core/gallery',
},
'uuid-22': {
clientId: 'uuid-22',
name: 'core/block',
},
'uuid-24': {
clientId: 'uuid-24',
name: 'core/columns',
},
'uuid-26': {
clientId: 'uuid-26',
name: 'core/column',
},
'uuid-28': {
clientId: 'uuid-28',
name: 'core/column',
},
'uuid-30': {
clientId: 'uuid-30',
name: 'core/paragraph',
},
} )
),
attributes: new Map(
Object.entries( {
'uuid-2': {},
'uuid-4': {},
'uuid-6': {},
'uuid-8': {},
'uuid-10': {},
'uuid-12': {},
'uuid-14': {},
'uuid-16': {},
'uuid-18': {},
'uuid-20': {},
'uuid-22': {},
'uuid-24': {},
'uuid-26': {},
'uuid-28': {},
'uuid-30': {},
} )
),
order: new Map(
Object.entries( {
'': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ],
'uuid-2': [],
'uuid-4': [],
'uuid-6': [],
'uuid-8': [],
'uuid-10': [ 'uuid-12', 'uuid-14' ],
'uuid-12': [ 'uuid-16' ],
'uuid-14': [ 'uuid-18' ],
'uuid-16': [],
'uuid-18': [ 'uuid-24' ],
'uuid-20': [],
'uuid-22': [],
'uuid-24': [ 'uuid-26', 'uuid-28' ],
'uuid-26': [],
'uuid-28': [ 'uuid-30' ],
} )
),
parents: new Map(
Object.entries( {
'uuid-6': '',
'uuid-8': '',
'uuid-10': '',
'uuid-22': '',
'uuid-12': 'uuid-10',
'uuid-14': 'uuid-10',
'uuid-16': 'uuid-12',
'uuid-18': 'uuid-14',
'uuid-24': 'uuid-18',
'uuid-26': 'uuid-24',
'uuid-28': 'uuid-24',
'uuid-30': 'uuid-28',
} )
),
},
};
expect( getClientIdsWithDescendants( state ) ).toEqual( [
'uuid-6',
'uuid-8',
'uuid-10',
'uuid-12',
'uuid-16',
'uuid-14',
'uuid-18',
'uuid-24',
'uuid-26',
'uuid-28',
'uuid-30',
'uuid-22',
] );
} );
} );
describe( 'getBlockCount', () => {
it( 'should return the number of top-level blocks in the post', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: { clientId: '23', name: 'core/heading' },
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
23: {},
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
},
};
expect( getBlockCount( state ) ).toBe( 2 );
} );
it( 'should return the number of blocks in a nested context', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
123: { clientId: '123', name: 'core/columns' },
456: { clientId: '456', name: 'core/paragraph' },
789: { clientId: '789', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
123: {},
456: {},
789: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123' ],
123: [ '456', '789' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
456: '123',
789: '123',
} )
),
},
};
expect( getBlockCount( state, '123' ) ).toBe( 2 );
} );
} );
describe( 'hasSelectedBlock', () => {
it( 'should return false if no selection', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( hasSelectedBlock( state ) ).toBe( false );
} );
it( 'should return false if multi-selection', () => {
const state = {
selection: {
selectionStart: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
selectionEnd: {
clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189',
},
},
};
expect( hasSelectedBlock( state ) ).toBe( false );
} );
it( 'should return true if singular selection', () => {
const state = {
selection: {
selectionStart: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
selectionEnd: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
},
};
expect( hasSelectedBlock( state ) ).toBe( true );
} );
} );
describe( 'getGlobalBlockCount', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
123: { clientId: '123', name: 'core/heading' },
456: { clientId: '456', name: 'core/paragraph' },
789: { clientId: '789', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
123: {},
456: {},
789: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123', '456' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
456: '',
} )
),
},
};
it( 'should return the global number of blocks in the post', () => {
expect( getGlobalBlockCount( state ) ).toBe( 2 );
} );
it( 'should return the global number of blocks in the post of a given type', () => {
expect( getGlobalBlockCount( state, 'core/paragraph' ) ).toBe( 1 );
} );
it( 'should return 0 if no blocks exist', () => {
const emptyState = {
blocks: {
byClientId: new Map(),
attributes: new Map(),
order: new Map(),
parents: new Map(),
},
};
expect( getGlobalBlockCount( emptyState ) ).toBe( 0 );
expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe(
0
);
} );
} );
describe( 'getBlocksByName', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
123: { clientId: '123', name: 'core/heading' },
456: { clientId: '456', name: 'core/paragraph' },
789: { clientId: '789', name: 'core/paragraph' },
1011: { clientId: '1011', name: 'core/group' },
1213: { clientId: '1213', name: 'core/paragraph' },
1415: { clientId: '1213', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
123: {},
456: {},
789: {},
1011: {},
1213: {},
1415: {},
} )
),
order: new Map(
Object.entries( {
'': [ '123', '456', '1011' ],
1011: [ '1415', '1213' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
456: '',
1011: '',
1213: '1011',
1415: '1011',
} )
),
},
};
it( 'should return the clientIds of blocks of a given type', () => {
expect( getBlocksByName( state, 'core/heading' ) ).toStrictEqual( [
'123',
] );
} );
it( 'should return the clientIds of blocks of a given type even if blocks are nested', () => {
expect( getBlocksByName( state, 'core/paragraph' ) ).toStrictEqual(
[ '456', '1415', '1213' ]
);
} );
it( 'Should return empty array if no blocks match. The empty array should be the same reference', () => {
const result = getBlocksByName( state, 'test/missing' );
expect( getBlocksByName( state, 'test/missing' ) ).toStrictEqual(
[]
);
expect( getBlocksByName( state, 'test/missing2' ) === result ).toBe(
true
);
} );
} );
describe( 'getSelectedBlockClientId', () => {
it( 'should return null if no block is selected', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getSelectedBlockClientId( state ) ).toBe( null );
} );
it( 'should return null if there is multi selection', () => {
const state = {
selection: {
selectionStart: { clientId: '23' },
selectionEnd: { clientId: '123' },
},
};
expect( getSelectedBlockClientId( state ) ).toBe( null );
} );
it( 'should return the selected block ClientId', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: {
name: 'fake block',
},
} )
),
},
selection: {
selectionStart: { clientId: '23' },
selectionEnd: { clientId: '23' },
},
};
expect( getSelectedBlockClientId( state ) ).toEqual( '23' );
} );
} );
describe( 'getSelectedBlock', () => {
it( 'should return null if no block is selected', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: { clientId: '23', name: 'core/heading' },
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
23: {},
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '23', '123' ],
23: [],
123: [],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
tree: new Map(
Object.entries( {
23: {
clientId: '23',
name: 'core/heading',
attributes: {},
innerBlocks: [],
},
} )
),
},
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getSelectedBlock( state ) ).toBe( null );
} );
it( 'should return null if there is multi selection', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: { clientId: '23', name: 'core/heading' },
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
23: {},
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '23', '123' ],
23: [],
123: [],
} )
),
parents: new Map(
Object.entries( {
123: '',
23: '',
} )
),
tree: new Map(
Object.entries( {
23: {
clientId: '23',
name: 'core/heading',
attributes: {},
innerBlocks: [],
},
} )
),
},
selection: {
selectionStart: { clientId: '23' },
selectionEnd: { clientId: '123' },
},
};
expect( getSelectedBlock( state ) ).toBe( null );
} );
it( 'should return the selected block', () => {
const state = {
blocks: {
byClientId: new Map(
Object.entries( {
23: { clientId: '23', name: 'core/heading' },
123: { clientId: '123', name: 'core/paragraph' },
} )
),
attributes: new Map(
Object.entries( {
23: {},
123: {},
} )
),
order: new Map(
Object.entries( {
'': [ '23', '123' ],
23: [],
123: [],
} )
),
parents: new Map(
Object.entries( {
123: '',
23: '',
} )
),
tree: new Map(
Object.entries( {
23: {
clientId: '23',
name: 'core/heading',
attributes: {},
innerBlocks: [],
},
} )
),
controlledInnerBlocks: {},
},
selection: {
selectionStart: { clientId: '23' },
selectionEnd: { clientId: '23' },
},
};
expect( getSelectedBlock( state ) ).toEqual(
getBlock( state, '23' )
);
} );
} );
describe( 'getBlockRootClientId', () => {
it( 'should return null if the block does not exist', () => {
const state = {
blocks: {
order: new Map(),
parents: new Map(),
},
};
expect( getBlockRootClientId( state, 56 ) ).toBeNull();
} );
it( 'should return root ClientId relative the block ClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
23: '',
456: '123',
56: '123',
} )
),
},
};
expect( getBlockRootClientId( state, '56' ) ).toBe( '123' );
} );
} );
describe( 'getBlockHierarchyRootClientId', () => {
it( 'should return the given block if the block has no parents', () => {
const state = {
blocks: {
order: new Map(),
parents: new Map(),
},
};
expect( getBlockHierarchyRootClientId( state, '56' ) ).toBe( '56' );
} );
it( 'should return root ClientId relative the block ClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ 'a', 'b' ],
a: [ 'c', 'd' ],
} )
),
parents: new Map(
Object.entries( {
a: '',
b: '',
c: 'a',
d: 'a',
} )
),
},
};
expect( getBlockHierarchyRootClientId( state, 'c' ) ).toBe( 'a' );
} );
it( 'should return the top level root ClientId relative the block ClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ 'a', 'b' ],
a: [ 'c', 'd' ],
d: [ 'e' ],
} )
),
parents: new Map(
Object.entries( {
a: '',
b: '',
c: 'a',
d: 'a',
e: 'd',
} )
),
},
};
expect( getBlockHierarchyRootClientId( state, 'e' ) ).toBe( 'a' );
} );
} );
describe( 'getSelectedBlockClientIds', () => {
it( 'should return empty if there is no selection', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
123: '',
23: '',
} )
),
},
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getSelectedBlockClientIds( state ) ).toEqual( [] );
} );
it( 'should return the selected block clientId if there is a selection', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '2' },
},
};
expect( getSelectedBlockClientIds( state ) ).toEqual( [ '2' ] );
} );
it( 'should return selected block clientIds if there is multi selection', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
expect( getSelectedBlockClientIds( state ) ).toEqual( [
'4',
'3',
'2',
] );
} );
it( 'should return selected block clientIds if there is multi selection (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
4: [ '9', '8', '7', '6' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
6: '4',
7: '4',
8: '4',
9: '4',
} )
),
},
selection: {
selectionStart: { clientId: '7' },
selectionEnd: { clientId: '9' },
},
};
expect( getSelectedBlockClientIds( state ) ).toEqual( [
'9',
'8',
'7',
] );
} );
} );
describe( 'getMultiSelectedBlockClientIds', () => {
it( 'should return empty if there is no multi selection', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [] );
} );
it( 'should return selected block clientIds if there is multi selection', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [
'4',
'3',
'2',
] );
} );
it( 'should return selected block clientIds if there is multi selection (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
4: [ '9', '8', '7', '6' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
6: '4',
7: '4',
8: '4',
9: '4',
} )
),
},
selection: {
selectionStart: { clientId: '7' },
selectionEnd: { clientId: '9' },
},
};
expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [
'9',
'8',
'7',
] );
} );
} );
describe( 'getMultiSelectedBlocks', () => {
it( 'should return the same reference on subsequent invocations of empty selection', () => {
const state = {
blocks: {
byClientId: new Map(),
attributes: new Map(),
order: new Map(),
parents: new Map(),
},
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getMultiSelectedBlocks( state ) ).toBe(
getMultiSelectedBlocks( state )
);
} );
} );
describe( 'getMultiSelectedBlocksStartClientId', () => {
it( 'returns null if there is no multi selection', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getMultiSelectedBlocksStartClientId( state ) ).toBeNull();
} );
it( 'returns multi selection start', () => {
const state = {
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
expect( getMultiSelectedBlocksStartClientId( state ) ).toBe( '2' );
} );
} );
describe( 'getMultiSelectedBlocksEndClientId', () => {
it( 'returns null if there is no multi selection', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( getMultiSelectedBlocksEndClientId( state ) ).toBeNull();
} );
it( 'returns multi selection end', () => {
const state = {
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
expect( getMultiSelectedBlocksEndClientId( state ) ).toBe( '4' );
} );
} );
describe( 'getBlockOrder', () => {
it( 'should return the ordered block ClientIds of top-level blocks by default', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getBlockOrder( state ) ).toEqual( [ '123', '23' ] );
} );
it( 'should return the ordered block ClientIds at a specified rootClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
456: '123',
} )
),
},
};
expect( getBlockOrder( state, '123' ) ).toEqual( [ '456' ] );
} );
} );
describe( 'getBlockIndex', () => {
it( 'should return the block order', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getBlockIndex( state, '23' ) ).toBe( 1 );
} );
it( 'should return the block order (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
56: '123',
456: '123',
} )
),
},
};
expect( getBlockIndex( state, '56' ) ).toBe( 1 );
} );
} );
describe( 'getPreviousBlockClientId', () => {
it( 'should return the previous block', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getPreviousBlockClientId( state, '23' ) ).toEqual( '123' );
} );
it( 'should return the previous block (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
456: '123',
56: '123',
} )
),
},
};
expect( getPreviousBlockClientId( state, '56', '123' ) ).toEqual(
'456'
);
} );
it( 'should return null for the first block', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getPreviousBlockClientId( state, '123' ) ).toBeNull();
} );
it( 'should return null for the first block (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
456: '123',
56: '123',
} )
),
},
};
expect(
getPreviousBlockClientId( state, '456', '123' )
).toBeNull();
} );
} );
describe( 'getNextBlockClientId', () => {
it( 'should return the following block', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getNextBlockClientId( state, '123' ) ).toEqual( '23' );
} );
it( 'should return the following block (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
456: '123',
56: '123',
} )
),
},
};
expect( getNextBlockClientId( state, '456', '123' ) ).toEqual(
'56'
);
} );
it( 'should return null for the last block', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
} )
),
},
};
expect( getNextBlockClientId( state, '23' ) ).toBeNull();
} );
it( 'should return null for the last block (nested context)', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '123', '23' ],
123: [ '456', '56' ],
} )
),
parents: new Map(
Object.entries( {
23: '',
123: '',
456: '123',
56: '123',
} )
),
},
};
expect( getNextBlockClientId( state, '56', '123' ) ).toBeNull();
} );
} );
describe( 'isBlockSelected', () => {
it( 'should return true if the block is selected', () => {
const state = {
selection: {
selectionStart: { clientId: '123' },
selectionEnd: { clientId: '123' },
},
};
expect( isBlockSelected( state, '123' ) ).toBe( true );
} );
it( 'should return false if a multi-selection range exists', () => {
const state = {
selection: {
selectionStart: { clientId: '123' },
selectionEnd: { clientId: '124' },
},
};
expect( isBlockSelected( state, '123' ) ).toBe( false );
} );
it( 'should return false if the block is not selected', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( isBlockSelected( state, '23' ) ).toBe( false );
} );
} );
describe( 'hasSelectedInnerBlock', () => {
it( 'should return false if the selected block is a child of the given ClientId', () => {
const state = {
selection: {
selectionStart: { clientId: '5' },
selectionEnd: { clientId: '5' },
},
blocks: {
order: new Map(
Object.entries( {
4: [ '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '4',
2: '4',
3: '4',
} )
),
},
};
expect( hasSelectedInnerBlock( state, '4' ) ).toBe( false );
} );
it( 'should return true if the selected block is a child of the given ClientId', () => {
const state = {
selection: {
selectionStart: { clientId: '3' },
selectionEnd: { clientId: '3' },
},
blocks: {
order: new Map(
Object.entries( {
4: [ '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '4',
2: '4',
3: '4',
} )
),
},
};
expect( hasSelectedInnerBlock( state, '4' ) ).toBe( true );
} );
it( 'should return true if a multi selection exists that contains children of the block with the given ClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
6: [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '6',
2: '6',
3: '6',
4: '6',
5: '6',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
expect( hasSelectedInnerBlock( state, '6' ) ).toBe( true );
} );
it( 'should return false if a multi selection exists bot does not contains children of the block with the given ClientId', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
3: [ '2', '1' ],
6: [ '5', '4' ],
} )
),
parents: new Map(
Object.entries( {
1: '3',
2: '3',
4: '6',
5: '6',
} )
),
},
selection: {
selectionStart: { clientId: '5' },
selectionEnd: { clientId: '4' },
},
};
expect( hasSelectedInnerBlock( state, '3' ) ).toBe( false );
} );
} );
describe( 'isBlockWithinSelection', () => {
it( 'should return true if the block is selected but not the last', () => {
const state = {
selection: {
selectionStart: { clientId: '5' },
selectionEnd: { clientId: '3' },
},
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
};
expect( isBlockWithinSelection( state, '4' ) ).toBe( true );
} );
it( 'should return false if the block is the last selected', () => {
const state = {
selection: {
selectionStart: { clientId: '5' },
selectionEnd: { clientId: '3' },
},
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
};
expect( isBlockWithinSelection( state, '3' ) ).toBe( false );
} );
it( 'should return false if the block is not selected', () => {
const state = {
selection: {
selectionStart: { clientId: '5' },
selectionEnd: { clientId: '3' },
},
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
};
expect( isBlockWithinSelection( state, '2' ) ).toBe( false );
} );
it( 'should return false if there is no selection', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
};
expect( isBlockWithinSelection( state, '4' ) ).toBe( false );
} );
} );
describe( 'hasMultiSelection', () => {
it( 'should return false if no selection', () => {
const state = {
selection: {
selectionStart: {},
selectionEnd: {},
},
};
expect( hasMultiSelection( state ) ).toBe( false );
} );
it( 'should return false if singular selection', () => {
const state = {
selection: {
selectionStart: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
selectionEnd: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
},
};
expect( hasMultiSelection( state ) ).toBe( false );
} );
it( 'should return true if multi-selection', () => {
const state = {
selection: {
selectionStart: {
clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1',
},
selectionEnd: {
clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189',
},
},
};
expect( hasMultiSelection( state ) ).toBe( true );
} );
} );
describe( 'isBlockMultiSelected', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
it( 'should return true if the block is multi selected', () => {
expect( isBlockMultiSelected( state, '3' ) ).toBe( true );
} );
it( 'should return false if the block is not multi selected', () => {
expect( isBlockMultiSelected( state, '5' ) ).toBe( false );
} );
} );
describe( 'isFirstMultiSelectedBlock', () => {
const state = {
blocks: {
order: new Map(
Object.entries( {
'': [ '5', '4', '3', '2', '1' ],
} )
),
parents: new Map(
Object.entries( {
1: '',
2: '',
3: '',
4: '',
5: '',
} )
),
},
selection: {
selectionStart: { clientId: '2' },
selectionEnd: { clientId: '4' },
},
};
it( 'should return true if the block is first in multi selection', () => {
expect( isFirstMultiSelectedBlock( state, '4' ) ).toBe( true );
} );
it( 'should return false if the block is not first in multi selection', () => {
expect( isFirstMultiSelectedBlock( state, '3' ) ).toBe( false );
} );
} );
describe( 'getBlockMode', () => {
it( 'should return "visual" if unset', () => {
const state = {
blocksMode: {},
};
expect( getBlockMode( state, '123' ) ).toEqual( 'visual' );
} );
it( 'should return the block mode', () => {
const state = {
blocksMode: {
123: 'html',
},
};
expect( getBlockMode( state, '123' ) ).toEqual( 'html' );
} );
} );
describe( 'isTyping', () => {
it( 'should return the isTyping flag if the block is selected', () => {
const state = {
isTyping: true,
};
expect( isTyping( state ) ).toBe( true );
} );
it( 'should return false if the block is not selected', () => {
const state = {
isTyping: false,
};
expect( isTyping( state ) ).toBe( false );
} );
} );
describe( 'isDraggingBlocks', () => {
it( 'should return true if a block is being dragged', () => {
const state = {
draggedBlocks: [ 'block-client-id' ],
};
expect( isDraggingBlocks( state ) ).toBe( true );
} );
it( 'should return false if a block is not being dragged', () => {
const state = {
draggedBlocks: [],
};
expect( isDraggingBlocks( state ) ).toBe( false );
} );
} );
describe( 'getDraggedBlockClientIds', () => {
it( 'returns the draggedBlocks state', () => {
const draggedBlocks = [ 'block-client-id' ];
const state = {
draggedBlocks,
};
expect( getDraggedBlockClientIds( state ) ).toBe( draggedBlocks );
} );
} );
describe( 'isBlockBeingDragged', () => {
it( 'returns true if the given client id is one of the blocks being dragged', () => {
const state = {
draggedBlocks: [ 'block-1', 'block-2', 'block-3' ],
};
expect( isBlockBeingDragged( state, 'block-2' ) ).toBe( true );
} );
it( 'returns false if the given client id is not one of the blocks being dragged', () => {
const state = {
draggedBlocks: [ 'block-1', 'block-2', 'block-3' ],
};
expect( isBlockBeingDragged( state, 'block-4' ) ).toBe( false );
} );
it( 'returns false if no blocks are being dragged', () => {
const state = {
draggedBlocks: [],
};
expect( isBlockBeingDragged( state, 'block-1' ) ).toBe( false );
} );
} );
describe( 'isAncestorBeingDragged', () => {
it( 'returns true if the given client id is a child of one of the blocks being dragged', () =