UNPKG

@gechiui/block-editor

Version:
2,347 lines (2,146 loc) 86.3 kB
/** * External dependencies */ import { filter } from 'lodash'; /** * GeChiUI dependencies */ import { registerBlockType, unregisterBlockType, setFreeformContentHandlerName, } from '@gechiui/blocks'; import { RawHTML } from '@gechiui/element'; /** * Internal dependencies */ import * as selectors from '../selectors'; 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, isCaretWithinFormattedText, getBlockInsertionPoint, isBlockInsertionPointVisible, isSelectionEnabled, canInsertBlockType, canInsertBlocks, getInserterItems, getBlockTransformItems, isValidTemplate, getTemplate, getTemplateLock, getBlockListSettings, __experimentalGetBlockListSettingsForBlocks, __experimentalGetLastBlockAttributeChanges, getLowestCommonAncestorWithSelectedBlock, __experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames, __experimentalGetAllowedPatterns, __experimentalGetParsedPattern, __experimentalGetPatternsByBlockTypes, __unstableGetClientIdWithClientIdsTree, __unstableGetClientIdsTree, __experimentalGetPatternTransformItems, wasBlockJustInserted, } = selectors; describe( 'selectors', () => { let cachedSelectors; beforeAll( () => { cachedSelectors = filter( selectors, ( selector ) => selector.clear ); } ); 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/test-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' ], } ); setFreeformContentHandlerName( 'core/test-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/test-freeform' ); unregisterBlockType( 'core/post-content-child' ); setFreeformContentHandlerName( undefined ); } ); describe( 'getBlockName', () => { it( 'returns null if no block by clientId', () => { const state = { blocks: { byClientId: {}, attributes: {}, order: {}, parents: {}, }, }; const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); expect( name ).toBe( null ); } ); it( 'returns block name', () => { const state = { blocks: { byClientId: { 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', name: 'core/paragraph', }, }, attributes: { 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': {}, }, order: { '': [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ], 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1': [], }, parents: { '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: { 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 123: {}, }, order: { '': [ 123 ], 123: [], }, parents: { 123: '', }, tree: { 123: { clientId: 123, name: 'core/paragraph', attributes: {}, innerBlocks: [], }, }, controlledInnerBlocks: {}, }, }; expect( getBlock( state, 123 ) ).toBe( state.blocks.tree[ 123 ] ); } ); it( 'should return null if the block is not present in state', () => { const state = { blocks: { byClientId: {}, attributes: {}, order: {}, parents: {}, tree: { 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: { 23: { clientId: 23, name: 'core/heading' }, 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 23: {}, 123: {}, }, order: { '': [ 123, 23 ], }, parents: { 123: '', 23: '', }, tree: { '': { 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[ '' ].innerBlocks ); } ); } ); describe( 'getBlockParents', () => { it( 'should return parent blocks', () => { const state = { blocks: { parents: { 'client-id-01': '', 'client-id-02': 'client-id-01', 'client-id-03': 'client-id-02', 'client-id-04': 'client-id-03', }, byClientId: { '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: { '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: { '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', () => { it( 'should return the ids of any descendants, given an array of clientIds', () => { const state = { blocks: { byClientId: { '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: { '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: { '': [ '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: { '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: {}, }, }; expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ 'uuid-12', 'uuid-14', 'uuid-16', 'uuid-18', 'uuid-24', 'uuid-26', 'uuid-28', 'uuid-30', ] ); } ); } ); describe( 'getClientIdsWithDescendants', () => { it( 'should return the ids for top-level blocks and their descendants of any depth (for nested blocks).', () => { const state = { blocks: { byClientId: { '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: { '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: { '': [ '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: { '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-22', 'uuid-12', 'uuid-14', 'uuid-16', 'uuid-18', 'uuid-24', 'uuid-26', 'uuid-28', 'uuid-30', ] ); } ); } ); describe( 'getBlockCount', () => { it( 'should return the number of top-level blocks in the post', () => { const state = { blocks: { byClientId: { 23: { clientId: 23, name: 'core/heading' }, 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 23: {}, 123: {}, }, order: { '': [ 123, 23 ], }, }, }; expect( getBlockCount( state ) ).toBe( 2 ); } ); it( 'should return the number of blocks in a nested context', () => { const state = { blocks: { byClientId: { 123: { clientId: 123, name: 'core/columns' }, 456: { clientId: 456, name: 'core/paragraph' }, 789: { clientId: 789, name: 'core/paragraph' }, }, attributes: { 123: {}, 456: {}, 789: {}, }, order: { '': [ 123 ], 123: [ 456, 789 ], }, parents: { 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: { 123: { clientId: 123, name: 'core/heading' }, 456: { clientId: 456, name: 'core/paragraph' }, 789: { clientId: 789, name: 'core/paragraph' }, }, attributes: { 123: {}, 456: {}, 789: {}, }, order: { '': [ 123, 456 ], }, parents: { 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: {}, attributes: {}, order: {}, parents: {}, }, }; expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe( 0 ); } ); } ); 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: { 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: { 23: { clientId: 23, name: 'core/heading' }, 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 23: {}, 123: {}, }, order: { '': [ 23, 123 ], 23: [], 123: [], }, parents: { 23: '', 123: '', }, tree: { 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: { 23: { clientId: 23, name: 'core/heading' }, 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 23: {}, 123: {}, }, order: { '': [ 23, 123 ], 23: [], 123: [], }, parents: { 123: '', 23: '', }, tree: { 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: { 23: { clientId: 23, name: 'core/heading' }, 123: { clientId: 123, name: 'core/paragraph' }, }, attributes: { 23: {}, 123: {}, }, order: { '': [ 23, 123 ], 23: [], 123: [], }, parents: { 123: '', 23: '', }, tree: { 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: {}, parents: {}, }, }; expect( getBlockRootClientId( state, 56 ) ).toBeNull(); } ); it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 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: {}, parents: {}, }, }; expect( getBlockHierarchyRootClientId( state, '56' ) ).toBe( '56' ); } ); it( 'should return root ClientId relative the block ClientId', () => { const state = { blocks: { order: { '': [ 'a', 'b' ], a: [ 'c', 'd' ], }, parents: { 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: { '': [ 'a', 'b' ], a: [ 'c', 'd' ], d: [ 'e' ], }, parents: { 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: { '': [ 123, 23 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, parents: { 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: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, selection: { selectionStart: {}, selectionEnd: {}, }, }; expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [] ); } ); it( 'should return selected block clientIds if there is multi selection', () => { const state = { blocks: { order: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], 4: [ 9, 8, 7, 6 ], }, parents: { 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: {}, attributes: {}, order: {}, parents: {}, }, 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: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getBlockOrder( state ) ).toEqual( [ 123, 23 ] ); } ); it( 'should return the ordered block ClientIds at a specified rootClientId', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456 ], }, parents: { 23: '', 123: '', 456: 123, }, }, }; expect( getBlockOrder( state, '123' ) ).toEqual( [ 456 ] ); } ); } ); describe( 'getBlockIndex', () => { it( 'should return the block order', () => { const state = { blocks: { order: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getBlockIndex( state, 23 ) ).toBe( 1 ); } ); it( 'should return the block order (nested context)', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 23: '', 123: '', 56: 123, 456: 123, }, }, }; expect( getBlockIndex( state, 56 ) ).toBe( 1 ); } ); } ); describe( 'getPreviousBlockClientId', () => { it( 'should return the previous block', () => { const state = { blocks: { order: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getPreviousBlockClientId( state, 23 ) ).toEqual( 123 ); } ); it( 'should return the previous block (nested context)', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 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: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getPreviousBlockClientId( state, 123 ) ).toBeNull(); } ); it( 'should return null for the first block (nested context)', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 23: '', 123: '', 456: 123, 56: 123, }, }, }; expect( getPreviousBlockClientId( state, 456, '123' ) ).toBeNull(); } ); } ); describe( 'getNextBlockClientId', () => { it( 'should return the following block', () => { const state = { blocks: { order: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getNextBlockClientId( state, 123 ) ).toEqual( 23 ); } ); it( 'should return the following block (nested context)', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 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: { '': [ 123, 23 ], }, parents: { 23: '', 123: '', }, }, }; expect( getNextBlockClientId( state, 23 ) ).toBeNull(); } ); it( 'should return null for the last block (nested context)', () => { const state = { blocks: { order: { '': [ 123, 23 ], 123: [ 456, 56 ], }, parents: { 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: { 4: [ 3, 2, 1 ], }, parents: { 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: { 4: [ 3, 2, 1 ], }, parents: { 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: { 6: [ 5, 4, 3, 2, 1 ], }, parents: { 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: { 3: [ 2, 1 ], 6: [ 5, 4 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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: { '': [ 5, 4, 3, 2, 1 ], }, parents: { 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', () => { const state = { draggedBlocks: [ 'block-1_grandparent' ], blocks: { parents: { 'block-1': 'block-1_parent', 'block-1_parent': 'block-1_grandparent', }, }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false if the given client id does not have an ancestor being dragged', () => { const state = { draggedBlocks: [ 'block-2_grandparent' ], blocks: { parents: { 'block-1': 'block-1_parent', 'block-1_parent': 'block-1_grandparent', }, }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( false ); } ); it( 'returns false if no blocks are being dragged', () => { const state = { draggedBlocks: [], blocks: { parents: { 'block-1': 'block-1_parent', 'block-1_parent': 'block-1_grandparent', }, }, }; expect( isAncestorBeingDragged( state, 'block-1' ) ).toBe( false ); } ); } ); describe( 'isCaretWithinFormattedText', () => { it( 'returns true if the isCaretWithinFormattedText state is also true', () => { const state = { isCaretWithinFormattedText: true, }; expect( isCaretWithinFormattedText( state ) ).toBe( true ); } ); it( 'returns false if the isCaretWithinFormattedText state is also false', () => { const state = { isCaretWithinFormattedText: false, }; expect( isCaretWithinFormattedText( state ) ).toBe( false ); } ); } ); describe( 'isSelectionEnabled', () => { it( 'should return true if selection is enable', () => { const state = { isSelectionEnabled: true, }; expect( isSelectionEnabled( state ) ).toBe( true ); } ); it( 'should return false if selection is disabled', () => { const state = { isSelectionEnabled: false, }; expect( isSelectionEnabled( state ) ).toBe( false ); } ); } ); describe( 'getBlockInsertionPoint', () => { it( 'should return the explicitly assigned insertion point', () => { const state = { selection: { selectionStart: { clientId: 'clientId2' }, selectionEnd: { clientId: 'clientId2' }, }, blocks: { byClientId: { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, attributes: { clientId1: {}, clientId2: {}, }, order: { '': [ 'clientId1' ], clientId1: [ 'clientId2' ], clientId2: [], }, parents: { clientId1: '', clientId2: 'clientId1', }, }, insertionPoint: { rootClientId: undefined, index: 0, }, }; expect( getBlockInsertionPoint( state ) ).toEqual( { rootClientId: undefined, index: 0, } ); } ); it( 'should return an object for the selected block', () => { const state = { selection: { selectionStart: { clientId: 'clientId1' }, selectionEnd: { clientId: 'clientId1' }, }, blocks: { byClientId: { clientId1: { clientId: 'clientId1' }, }, attributes: { clientId1: {}, }, order: { '': [ 'clientId1' ], clientId1: [], }, parents: { clientId1: '', }, }, insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { rootClientId: undefined, index: 1, } ); } ); it( 'should return an object for the nested selected block', () => { const state = { selection: { selectionStart: { clientId: 'clientId2' }, selectionEnd: { clientId: 'clientId2' }, }, blocks: { byClientId: { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, attributes: { clientId1: {}, clientId2: {}, }, order: { '': [ 'clientId1' ], clientId1: [ 'clientId2' ], clientId2: [], }, parents: { clientId1: '', clientId2: 'clientId1', }, }, insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { rootClientId: 'clientId1', index: 1, } ); } ); it( 'should return an object for the last multi selected clientId', () => { const state = { selection: { selectionStart: { clientId: 'clientId1' }, selectionEnd: { clientId: 'clientId2' }, }, blocks: { byClientId: { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, attributes: { clientId1: {}, clientId2: {}, }, order: { '': [ 'clientId1', 'clientId2' ], clientId1: [], clientId2: [], }, parents: { clientId1: '', clientId2: '', }, }, insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { rootClientId: undefined, index: 2, } ); } ); it( 'should return an object for the last block if no selection', () => { const state = { selection: { selectionStart: {}, selectionEnd: {}, }, blocks: { byClientId: { clientId1: { clientId: 'clientId1' }, clientId2: { clientId: 'clientId2' }, }, attributes: { clientId1: {}, clientId2: {}, }, order: { '': [ 'clientId1', 'clientId2' ], clientId1: [], clientId2: [], }, parents: { clientId1: '', clientId2: '', }, }, insertionPoint: null, }; expect( getBlockInsertionPoint( state ) ).toEqual( { rootClientId: undefined, index: 2, } ); } ); } ); describe( 'isBlockInsertionPointVisible', () => { it( 'should return false if no assigned insertion point', () => { const state = { insertionPoint: null, }; expect( isBlockInsertionPointVisible( state ) ).toBe( false ); } ); it( 'should return true if assigned insertion point', () => { const state = { insertionPoint: { rootClientId: undefined, index: 5, }, }; expect( isBlockInsertionPointVisible( state ) ).toBe( true ); } ); } ); describe( 'canInsertBlockType', () => { it( 'should deny blocks that are not registered', () => { const state = { blocks: { byClientId: {}, attributes: {}, }, blockListSettings: {}, settings: {}, }; expect( canInsertBlockType( state, 'core/invalid' ) ).toBe( false ); } ); it( 'should deny blocks that are not allowed by the editor', () => { const state = { blocks: { byClientId: {}, attributes: {}, }, blockListSettings: {}, settings: { allowedBlockTypes: [], }, }; expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); } ); it( 'should allow blocks that are allowed by the editor', () => { const state = { blocks: { byClientId: {}, attributes: {}, }, blockListSettings: {}, settings: { allowedBlockTypes: [ 'core/test-block-a' ], }, }; expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( true ); } ); it( 'should deny blocks when the editor has a template lock', () => { const state = { blocks: { byClientId: {}, attributes: {}, }, blockListSettings: {}, settings: { templateLock: 'all', }, }; expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); } ); it( 'should deny blocks that restrict parent from being inserted into the root', () => { const state = { blocks: { byClientId: {}, attributes: {}, }, blockListSettings: {}, settings: {}, }; expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( false ); } ); it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { const state = { blocks: { byClientId: { block1: { name: 'core/test-block-a' }, }, attributes: { block1: {}, }, }, blockListSettings: {}, settings: {}, }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( false ); } ); it( 'should allow blocks to be inserted into an allowed parent', () => { const state = { blocks: { byClientId: { block1: { name: 'core/test-block-b' }, }, attributes: { block1: {}, }, }, blockListSettings: { block1: {}, }, settings: {}, }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); } ); it( 'should deny blocks from being inserted into a block that does not allow inner blocks', () => { const state = { blocks: { byClientId: { block1: { name: 'core/test-block-b' }, }, attributes: { block1: {}, }, }, blockListSettings: { block1: {}, }, settings: {}, }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); } ); it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { const state = { blocks: { byClientId: { block1: { name: 'core/test-block-a' }, }, attributes: { block1: {}, }, }, blockListSettings: { block1: { allowedBlocks: [ 'core/test-block-c' ], }, }, settings: {}, }; expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( false ); } ); it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { const state = { blocks: { byClientId: { block1: { name: 'core/test-block-a' }, }, attributes: { block1: {}