UNPKG

@wordpress/block-editor

Version:
1,690 lines (1,574 loc) 45.2 kB
/** * WordPress dependencies */ import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; /** * Internal dependencies */ import { isBlockInterfaceHidden, getLastInsertedBlocksClientIds, isBlockSubtreeDisabled, getEnabledClientIdsTree, getEnabledBlockParents, getExpandedBlock, isDragging, getBlockStyles, isEditLockedBlock, isMoveLockedBlock, isRemoveLockedBlock, isLockedBlock, isBlockHiddenAnywhere, isBlockHiddenAtViewport, getViewportModalClientIds, isSectionBlock, getParentSectionBlock, } from '../private-selectors'; import { getBlockEditingMode } from '../selectors'; import { deviceTypeKey } from '../private-keys'; describe( 'private selectors', () => { describe( 'isBlockInterfaceHidden', () => { it( 'should return the true if toggled true in state', () => { const state = { isBlockInterfaceHidden: true, }; expect( isBlockInterfaceHidden( state ) ).toBe( true ); } ); it( 'should return false if toggled false in state', () => { const state = { isBlockInterfaceHidden: false, }; expect( isBlockInterfaceHidden( state ) ).toBe( false ); } ); } ); describe( 'getLastInsertedBlocksClientIds', () => { it( 'should return undefined if no blocks have been inserted', () => { const state = { lastBlockInserted: {}, }; expect( getLastInsertedBlocksClientIds( state ) ).toEqual( undefined ); } ); it( 'should return clientIds if blocks have been inserted', () => { const state = { lastBlockInserted: { clientIds: [ '123456', '78910' ], }, }; expect( getLastInsertedBlocksClientIds( state ) ).toEqual( [ '123456', '78910', ] ); } ); } ); describe( 'isBlockSubtreeDisabled', () => { const baseState = { settings: {}, blocks: { byClientId: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', {} ], // Header [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', {} ], // Group [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', {} ], // | Post Title [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', {} ], // | Post Content [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', {} ], // | | Paragraph [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', {} ], // | | Paragraph ] ), order: new Map( [ [ '', [ '6cf70164-9097-4460-bcbf-200560546988', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], ], [ '6cf70164-9097-4460-bcbf-200560546988', [] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', [] ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', [] ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [] ], ] ), parents: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', '' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], ] ), blockEditingModes: new Map( [] ), }, blockListSettings: { 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337': {}, '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, derivedBlockEditingModes: new Map( [] ), }; const hasContentRoleAttribute = jest.fn( () => false ); const get = jest.fn( () => 'edit' ); getBlockEditingMode.registry = { select: jest.fn( () => ( { hasContentRoleAttribute, get, } ) ), }; it( 'should return false when top level block is not disabled', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [] ), }, derivedBlockEditingModes: new Map( [] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( false ); } ); it( 'should return true when top level block is disabled and there are no editing modes within it', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), }, derivedBlockEditingModes: new Map( [ [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], ] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( true ); } ); it( 'should return true when top level block is disabled via inheritance and there are no editing modes within it', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), }, derivedBlockEditingModes: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], ] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( true ); } ); it( 'should return true when top level block is disabled and there are disabled editing modes within it', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], ] ), }, derivedBlockEditingModes: new Map( [ [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], ] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( true ); } ); it( 'should return false when top level block is disabled and there are non-disabled editing modes within it', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), }, derivedBlockEditingModes: new Map( [ [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], ] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( false ); } ); it( 'should return false when top level block is disabled via inheritance and there are non-disabled editing modes within it', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ '', 'disabled' ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], ] ), }, derivedBlockEditingModes: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'disabled' ], ] ), }; expect( isBlockSubtreeDisabled( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toBe( false ); } ); } ); describe( 'getEnabledClientIdsTree', () => { const baseState = { settings: {}, blocks: { byClientId: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', {} ], // Header [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', {} ], // Group [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', {} ], // | Post Title [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', {} ], // | Post Content [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', {} ], // | | Paragraph [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', {} ], // | | Paragraph ] ), order: new Map( [ [ '', [ '6cf70164-9097-4460-bcbf-200560546988', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], ], [ '6cf70164-9097-4460-bcbf-200560546988', [] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', [] ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', [] ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [] ], ] ), parents: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', '' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], ] ), }, blockListSettings: { 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337': {}, '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': {}, }, }; getEnabledClientIdsTree.registry = { select: jest.fn( () => ( {} ) ), }; it( 'should return tree containing only clientId and innerBlocks', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [] ), }, derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { clientId: '6cf70164-9097-4460-bcbf-200560546988', innerBlocks: [], }, { clientId: 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', innerBlocks: [ { clientId: 'b26fc763-417d-4f01-b81c-2ec61e14a972', innerBlocks: [], }, { clientId: '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', innerBlocks: [ { clientId: 'b3247f75-fd94-4fef-97f9-5bfd162cc416', innerBlocks: [], }, { clientId: 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', innerBlocks: [], }, ], }, ], }, ] ); } ); it( 'should return a subtree when rootBlockClientId is given', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [] ), }, derivedBlockEditingModes: new Map( [] ), }; expect( getEnabledClientIdsTree( state, 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) ).toEqual( [ { clientId: 'b26fc763-417d-4f01-b81c-2ec61e14a972', innerBlocks: [], }, { clientId: '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', innerBlocks: [ { clientId: 'b3247f75-fd94-4fef-97f9-5bfd162cc416', innerBlocks: [], }, { clientId: 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', innerBlocks: [], }, ], }, ] ); } ); it( 'should filter out disabled blocks', () => { const state = { ...baseState, blocks: { ...baseState.blocks, blockEditingModes: new Map( [ [ '', 'disabled' ], [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', 'contentOnly', ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'contentOnly', ], ] ), }, derivedBlockEditingModes: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', 'disabled' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), }; expect( getEnabledClientIdsTree( state ) ).toEqual( [ { clientId: 'b26fc763-417d-4f01-b81c-2ec61e14a972', innerBlocks: [], }, { clientId: '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', innerBlocks: [ { clientId: 'b3247f75-fd94-4fef-97f9-5bfd162cc416', innerBlocks: [], }, { clientId: 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', innerBlocks: [], }, ], }, ] ); } ); } ); describe( 'getEnabledBlockParents', () => { it( 'should return an empty array if block is at the root', () => { const state = { settings: {}, blocks: { parents: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', '' ], ] ), order: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', [] ], [ '', [ '6cf70164-9097-4460-bcbf-200560546988' ] ], ] ), blockEditingModes: new Map(), }, derivedBlockEditingModes: new Map(), }; expect( getEnabledBlockParents( state, '6cf70164-9097-4460-bcbf-200560546988' ) ).toEqual( [] ); } ); it( 'should return non-disabled parents', () => { const state = { settings: {}, blocks: { parents: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ '4c2b7140-fffd-44b4-b2a7-820c670a6514', 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ] ), order: new Map( [ [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], ] ), blockEditingModes: new Map( [ [ '', 'disabled' ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', 'default' ], ] ), }, derivedBlockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], ] ), blockListSettings: {}, }; expect( getEnabledBlockParents( state, '4c2b7140-fffd-44b4-b2a7-820c670a6514' ) ).toEqual( [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' ] ); } ); it( 'should order from bottom to top if ascending is true', () => { const state = { settings: {}, blocks: { parents: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ], [ '4c2b7140-fffd-44b4-b2a7-820c670a6514', 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', ], ] ), order: new Map( [ [ '', [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ] ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' ], ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c' ], ], [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [ '4c2b7140-fffd-44b4-b2a7-820c670a6514' ], ], ] ), blockEditingModes: new Map( [ [ '', 'disabled' ], [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'default' ], ] ), }, derivedBlockEditingModes: new Map( [ [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), blockListSettings: {}, }; expect( getEnabledBlockParents( state, '4c2b7140-fffd-44b4-b2a7-820c670a6514', true ) ).toEqual( [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', ] ); } ); } ); describe( 'isDragging', () => { it( 'should return true if the dragging state is true', () => { const state = { isDragging: true, }; expect( isDragging( state ) ).toBe( true ); } ); it( 'should return false if the dragging state is false', () => { const state = { isDragging: false, }; expect( isDragging( state ) ).toBe( false ); } ); } ); describe( 'getExpandedBlock', () => { it( 'should return the expanded block', () => { const state = { expandedBlock: '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', }; expect( getExpandedBlock( state ) ).toBe( '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f' ); } ); } ); describe( 'getBlockStyles', () => { it( 'should return an empty object when no client IDs are provided', () => { const state = { blocks: { attributes: new Map(), }, }; const result = getBlockStyles( state, [] ); expect( result ).toEqual( {} ); } ); it( 'should return styles for a single block', () => { const state = { blocks: { attributes: new Map( [ [ 'block-1', { style: { color: 'red' } } ], ] ), }, }; const result = getBlockStyles( state, [ 'block-1' ] ); expect( result ).toEqual( { 'block-1': { color: 'red' }, } ); } ); it( 'should return styles for multiple blocks', () => { const state = { blocks: { attributes: new Map( [ [ 'block-1', { style: { color: 'red' } } ], [ 'block-2', { style: { fontSize: '16px' } } ], [ 'block-3', { style: { margin: '10px' } } ], ] ), }, }; const result = getBlockStyles( state, [ 'block-1', 'block-2', 'block-3', ] ); expect( result ).toEqual( { 'block-1': { color: 'red' }, 'block-2': { fontSize: '16px' }, 'block-3': { margin: '10px' }, } ); } ); it( 'should return undefined for blocks without styles', () => { const state = { blocks: { attributes: new Map( [ [ 'block-1', { style: { color: 'red' } } ], [ 'block-2', {} ], [ 'block-3', { style: { margin: '10px' } } ], ] ), }, }; const result = getBlockStyles( state, [ 'block-1', 'block-2', 'block-3', ] ); expect( result ).toEqual( { 'block-1': { color: 'red' }, 'block-2': undefined, 'block-3': { margin: '10px' }, } ); } ); it( 'should return undefined for non-existent blocks', () => { const state = { blocks: { attributes: new Map( [ [ 'block-1', { style: { color: 'red' } } ], ] ), }, }; const result = getBlockStyles( state, [ 'block-1', 'non-existent-block', ] ); expect( result ).toEqual( { 'block-1': { color: 'red' }, 'non-existent-block': undefined, } ); } ); } ); describe( 'isEditLockedBlock', () => { it( 'returns false when block has no lock attribute', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], ] ), attributes: new Map( [ [ 'block-1', {} ] ] ), }, }; expect( isEditLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns false when block has lock attribute but edit is false', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], ] ), attributes: new Map( [ [ 'block-1', { lock: { edit: false, move: true } } ], ] ), }, }; expect( isEditLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when block has lock attribute with edit set to true', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], ] ), attributes: new Map( [ [ 'block-1', { lock: { edit: true } } ], ] ), }, }; expect( isEditLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when block has no attributes', () => { const state = { blocks: { byClientId: new Map(), attributes: new Map(), }, }; expect( isEditLockedBlock( state, 'block-1' ) ).toBe( false ); } ); } ); describe( 'isMoveLockedBlock', () => { const createState = ( templateLock, blockLock ) => ( { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], [ 'parent-block', { clientId: 'parent-block' } ], ] ), attributes: new Map( [ [ 'block-1', blockLock ? { lock: blockLock } : {} ], [ 'parent-block', {} ], ] ), parents: new Map( [ [ 'block-1', 'parent-block' ], [ 'parent-block', '' ], ] ), }, settings: {}, blockListSettings: { 'parent-block': templateLock ? { templateLock } : {}, }, } ); it( 'returns false when block has no lock and no templateLock', () => { const state = createState( null, null ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when parent has templateLock set to "all"', () => { const state = createState( 'all', null ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when parent has templateLock set to "contentOnly"', () => { const state = createState( 'contentOnly', null ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when block has lock.move set to true', () => { const state = createState( null, { move: true } ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when block has lock.move set to false', () => { const state = createState( null, { move: false } ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'prioritizes block lock over template lock', () => { const state = createState( 'all', { move: false } ); expect( isMoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); } ); describe( 'isRemoveLockedBlock', () => { const createState = ( templateLock, blockLock ) => ( { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], [ 'parent-block', { clientId: 'parent-block' } ], ] ), attributes: new Map( [ [ 'block-1', blockLock ? { lock: blockLock } : {} ], [ 'parent-block', {} ], ] ), parents: new Map( [ [ 'block-1', 'parent-block' ], [ 'parent-block', '' ], ] ), }, settings: {}, blockListSettings: { 'parent-block': templateLock ? { templateLock } : {}, }, } ); it( 'returns false when block has no lock and no templateLock', () => { const state = createState( null, null ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when parent has templateLock set to "all"', () => { const state = createState( 'all', null ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when parent has templateLock set to "insert"', () => { const state = createState( 'insert', null ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when parent has templateLock set to "contentOnly"', () => { const state = createState( 'contentOnly', null ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when block has lock.remove set to true', () => { const state = createState( null, { remove: true } ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when block has lock.remove set to false', () => { const state = createState( null, { remove: false } ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'prioritizes block lock over template lock', () => { const state = createState( 'all', { remove: false } ); expect( isRemoveLockedBlock( state, 'block-1' ) ).toBe( false ); } ); } ); describe( 'isLockedBlock', () => { const createState = ( templateLock, blockLock ) => ( { blocks: { byClientId: new Map( [ [ 'block-1', { clientId: 'block-1' } ], [ 'parent-block', { clientId: 'parent-block' } ], ] ), attributes: new Map( [ [ 'block-1', blockLock ? { lock: blockLock } : {} ], [ 'parent-block', {} ], ] ), parents: new Map( [ [ 'block-1', 'parent-block' ], [ 'parent-block', '' ], ] ), }, settings: {}, blockListSettings: { 'parent-block': templateLock ? { templateLock } : {}, }, } ); it( 'returns false when block is not locked in any way', () => { const state = createState( null, null ); expect( isLockedBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true when block has lock.edit set to true', () => { const state = createState( null, { edit: true } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when block has lock.move set to true', () => { const state = createState( null, { move: true } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when block has lock.remove set to true', () => { const state = createState( null, { remove: true } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when parent has templateLock set to "all"', () => { const state = createState( 'all', null ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when block has multiple locks', () => { const state = createState( null, { edit: true, move: true, remove: true, } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns true when only one lock type is active', () => { const state = createState( null, { edit: false, move: true, remove: false, } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when all lock types are explicitly false', () => { const state = createState( null, { edit: false, move: false, remove: false, } ); expect( isLockedBlock( state, 'block-1' ) ).toBe( false ); } ); } ); describe( 'isBlockHiddenAnywhere', () => { beforeAll( () => { // Register a block with visibility support registerBlockType( 'core/test-block-with-visibility', { apiVersion: 3, save: () => null, category: 'text', title: 'Test Block With Visibility', supports: { visibility: true, }, } ); // Register a block without visibility support registerBlockType( 'core/test-block-without-visibility', { apiVersion: 3, save: () => null, category: 'text', title: 'Test Block Without Visibility', supports: { visibility: false, }, } ); // Register a block with no supports defined (defaults to true for visibility) registerBlockType( 'core/test-block-default-visibility', { apiVersion: 3, save: () => null, category: 'text', title: 'Test Block Default Visibility', } ); } ); afterAll( () => { unregisterBlockType( 'core/test-block-with-visibility' ); unregisterBlockType( 'core/test-block-without-visibility' ); unregisterBlockType( 'core/test-block-default-visibility' ); } ); it( 'should return false when block has visibility support and is not hidden', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: true, }, }, ], ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( false ); } ); it( 'should return true when block has visibility support and is hidden', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: false, }, }, ], ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( true ); } ); it( 'should return false when block does not have visibility support', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-without-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: false, }, }, ], ] ), }, }; // Even though blockVisibility is false, the block doesn't support visibility, // so it should return false expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( false ); } ); it( 'should return false when block has default visibility support and is not hidden', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-default-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: true, }, }, ], ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( false ); } ); it( 'should return true when block has default visibility support and is hidden', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-default-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: false, }, }, ], ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( true ); } ); it( 'should return false when block has no metadata', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', {} ] ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( false ); } ); it( 'should return false when block has metadata but no blockVisibility property', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { someOtherProperty: 'value', }, }, ], ] ), }, }; expect( isBlockHiddenAnywhere( state, 'block-1' ) ).toBe( false ); } ); it( 'should handle non-existent block gracefully', () => { const state = { blocks: { byClientId: new Map(), attributes: new Map(), }, }; // When block doesn't exist, getBlockName returns null, // and hasBlockSupport should handle null gracefully expect( isBlockHiddenAnywhere( state, 'non-existent-block' ) ).toBe( false ); } ); } ); describe( 'isBlockHiddenAtViewport', () => { beforeAll( () => { registerBlockType( 'core/test-block-with-visibility', { apiVersion: 3, save: () => null, category: 'text', title: 'Test Block With Visibility', supports: { visibility: true, }, } ); } ); afterAll( () => { unregisterBlockType( 'core/test-block-with-visibility' ); } ); it( 'returns true when block is hidden everywhere', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: false, }, }, ], ] ), }, }; expect( isBlockHiddenAtViewport( state, 'block-1', 'Desktop' ) ).toBe( true ); } ); it( 'returns visibility based on the requested viewport', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/test-block-with-visibility' }, ], ] ), attributes: new Map( [ [ 'block-1', { metadata: { blockVisibility: { viewport: { mobile: false, tablet: true, }, }, }, }, ], ] ), }, }; expect( isBlockHiddenAtViewport( state, 'block-1', 'Mobile' ) ).toBe( true ); expect( isBlockHiddenAtViewport( state, 'block-1', 'Tablet' ) ).toBe( false ); } ); } ); describe( 'isBlockHiddenAnywhere in different devices', () => { const createState = ( blockVisibility, deviceType = 'Desktop' ) => ( { settings: { [ deviceTypeKey ]: deviceType, }, blocks: { byClientId: new Map( [ [ 'test-block', { name: 'core/paragraph', attributes: { metadata: { blockVisibility, }, }, }, ], ] ), attributes: new Map( [ [ 'test-block', { metadata: { blockVisibility, }, }, ], ] ), }, } ); it( 'returns true when block is hidden at any viewport (mobile)', () => { const state = createState( { viewport: { mobile: false, tablet: true } }, 'Desktop' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( true ); } ); it( 'returns true when block is hidden on desktop', () => { const state = createState( { viewport: { desktop: false } }, 'Desktop' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( true ); } ); it( 'returns true when block is hidden on tablet', () => { const state = createState( { viewport: { mobile: true, tablet: false } }, 'Tablet' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( true ); } ); it( 'returns true when block is hidden on mobile', () => { const state = createState( { viewport: { mobile: false, tablet: true } }, 'Mobile' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( true ); } ); it( 'returns true when block is hidden at any viewport even if visible at current device', () => { const state = createState( { viewport: { mobile: false, tablet: true } }, 'Tablet' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( true ); } ); it( 'returns false when block is visible at all viewports', () => { const state = createState( { mobile: true, tablet: true, desktop: true }, 'Desktop' ); const result = isBlockHiddenAnywhere( state, 'test-block' ); expect( result ).toBe( false ); } ); } ); describe( 'getViewportModalClientIds', () => { it( 'should return null when modal is not open', () => { const state = { viewportModalClientIds: null, }; expect( getViewportModalClientIds( state ) ).toBeNull(); } ); it( 'should return client IDs when modal is open', () => { const clientIds = [ 'client-1', 'client-2' ]; const state = { viewportModalClientIds: clientIds, }; expect( getViewportModalClientIds( state ) ).toEqual( clientIds ); } ); } ); describe( 'isSectionBlock', () => { const createState = ( { blockName = 'core/group', patternName, disableContentOnlyForUnsyncedPatterns, templateLock, rootTemplateLock, } = {} ) => { const clientId = 'block-1'; const rootClientId = ''; const attributes = patternName ? { metadata: { patternName } } : {}; return { blocks: { byClientId: new Map( [ [ clientId, { name: blockName } ], ] ), attributes: new Map( [ [ clientId, attributes ] ] ), parents: new Map( [ [ clientId, rootClientId ] ] ), }, blockListSettings: { [ clientId ]: templateLock ? { templateLock } : {}, '': rootTemplateLock ? { templateLock: rootTemplateLock } : {}, }, settings: disableContentOnlyForUnsyncedPatterns !== undefined ? { disableContentOnlyForUnsyncedPatterns } : {}, editedContentOnlySection: undefined, }; }; it( 'should return true for blocks with patternName by default', () => { const state = createState( { patternName: 'my-pattern', } ); expect( isSectionBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'should return false for blocks with patternName when disableContentOnlyForUnsyncedPatterns is true', () => { const state = createState( { patternName: 'my-pattern', disableContentOnlyForUnsyncedPatterns: true, } ); expect( isSectionBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'should still return true for template parts when disableContentOnlyForUnsyncedPatterns is true', () => { const state = createState( { blockName: 'core/template-part', disableContentOnlyForUnsyncedPatterns: true, } ); expect( isSectionBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'should still return true for synced patterns (core/block) when disableContentOnlyForUnsyncedPatterns is true', () => { const state = createState( { blockName: 'core/block', disableContentOnlyForUnsyncedPatterns: true, } ); expect( isSectionBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'should return true for blocks with patternName when disableContentOnlyForUnsyncedPatterns is false', () => { const state = createState( { patternName: 'my-pattern', disableContentOnlyForUnsyncedPatterns: false, } ); expect( isSectionBlock( state, 'block-1' ) ).toBe( true ); } ); it( 'returns false when nested inside another section block', () => { const state = { blocks: { byClientId: new Map( [ [ 'outer-pattern', { name: 'core/group' } ], [ 'inner-pattern', { name: 'core/group' } ], ] ), attributes: new Map( [ [ 'outer-pattern', { metadata: { patternName: 'outer' } }, ], [ 'inner-pattern', { metadata: { patternName: 'inner' } }, ], ] ), parents: new Map( [ [ 'outer-pattern', '' ], [ 'inner-pattern', 'outer-pattern' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: undefined, }; // inner-pattern is nested inside outer-pattern (also a section), // so it is not considered a section itself. expect( isSectionBlock( state, 'inner-pattern' ) ).toBe( false ); } ); it( 'returns false when the block itself is the editedContentOnlySection', () => { const state = { ...createState( { patternName: 'my-pattern', } ), editedContentOnlySection: 'block-1', }; expect( isSectionBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns false when the block is nested within the editedContentOnlySection', () => { // Create a nested structure: outer-pattern > block-1 (with patternName) const state = { blocks: { byClientId: new Map( [ [ 'outer-pattern', { name: 'core/group' } ], [ 'block-1', { name: 'core/group' } ], ] ), attributes: new Map( [ [ 'outer-pattern', { metadata: { patternName: 'outer' } }, ], [ 'block-1', { metadata: { patternName: 'inner' } } ], ] ), parents: new Map( [ [ 'outer-pattern', '' ], [ 'block-1', 'outer-pattern' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: 'outer-pattern', }; // block-1 has a patternName, so would normally be a section, // but since its parent (outer-pattern) is being edited, it's not. expect( isSectionBlock( state, 'block-1' ) ).toBe( false ); } ); it( 'returns true for section blocks outside the editedContentOnlySection', () => { // Create a structure with two sibling patterns const state = { blocks: { byClientId: new Map( [ [ 'pattern-a', { name: 'core/group' } ], [ 'pattern-b', { name: 'core/group' } ], ] ), attributes: new Map( [ [ 'pattern-a', { metadata: { patternName: 'a' } } ], [ 'pattern-b', { metadata: { patternName: 'b' } } ], ] ), parents: new Map( [ [ 'pattern-a', '' ], [ 'pattern-b', '' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: 'pattern-a', }; // pattern-b is not the edited section and not within it expect( isSectionBlock( state, 'pattern-b' ) ).toBe( true ); } ); } ); describe( 'getParentSectionBlock', () => { it( 'returns undefined when there are no parent section blocks', () => { const state = { blocks: { byClientId: new Map( [ [ 'block-1', { name: 'core/paragraph' } ], ] ), attributes: new Map( [ [ 'block-1', {} ] ] ), parents: new Map( [ [ 'block-1', '' ] ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: undefined, }; expect( getParentSectionBlock( state, 'block-1' ) ).toBeUndefined(); } ); it( 'returns the parent section block clientId', () => { const state = { blocks: { byClientId: new Map( [ [ 'pattern-block', { name: 'core/group' } ], [ 'inner-block', { name: 'core/paragraph' } ], ] ), attributes: new Map( [ [ 'pattern-block', { metadata: { patternName: 'my-pattern' } }, ], [ 'inner-block', {} ], ] ), parents: new Map( [ [ 'pattern-block', '' ], [ 'inner-block', 'pattern-block' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: undefined, }; expect( getParentSectionBlock( state, 'inner-block' ) ).toBe( 'pattern-block' ); } ); it( 'returns undefined when the parent is the editedContentOnlySection', () => { const state = { blocks: { byClientId: new Map( [ [ 'pattern-block', { name: 'core/group' } ], [ 'inner-block', { name: 'core/paragraph' } ], ] ), attributes: new Map( [ [ 'pattern-block', { metadata: { patternName: 'my-pattern' } }, ], [ 'inner-block', {} ], ] ), parents: new Map( [ [ 'pattern-block', '' ], [ 'inner-block', 'pattern-block' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: 'pattern-block', }; // Since pattern-block is the edited section, it's no longer // considered a parent section for inner-block expect( getParentSectionBlock( state, 'inner-block' ) ).toBeUndefined(); } ); it( 'returns undefined for deeply nested blocks when an ancestor is the editedContentOnlySection', () => { // Structure: outer-pattern > nested-pattern > deep-block const state = { blocks: { byClientId: new Map( [ [ 'outer-pattern', { name: 'core/group' } ], [ 'nested-pattern', { name: 'core/group' } ], [ 'deep-block', { name: 'core/paragraph' } ], ] ), attributes: new Map( [ [ 'outer-pattern', { metadata: { patternName: 'outer' } }, ], [ 'nested-pattern', { metadata: { patternName: 'nested' } }, ], [ 'deep-block', {} ], ] ), parents: new Map( [ [ 'outer-pattern', '' ], [ 'nested-pattern', 'outer-pattern' ], [ 'deep-block', 'nested-pattern' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: 'outer-pattern', }; // When outer-pattern is being edited, nested-pattern is no longer // a section (tested above), so deep-block has no parent section expect( getParentSectionBlock( state, 'deep-block' ) ).toBeUndefined(); } ); it( 'returns the correct parent when editedContentOnlySection is set but not in ancestry', () => { // Structure: pattern-a > inner-block, pattern-b (sibling) const state = { blocks: { byClientId: new Map( [ [ 'pattern-a', { name: 'core/group' } ], [ 'inner-block', { name: 'core/paragraph' } ], [ 'pattern-b', { name: 'core/group' } ], ] ), attributes: new Map( [ [ 'pattern-a', { metadata: { patternName: 'a' } } ], [ 'inner-block', {} ], [ 'pattern-b', { metadata: { patternName: 'b' } } ], ] ), parents: new Map( [ [ 'pattern-a', '' ], [ 'inner-block', 'pattern-a' ], [ 'pattern-b', '' ], ] ), }, blockListSettings: {}, settings: {}, editedContentOnlySection: 'pattern-b', }; // pattern-a is not being edited, so inner-block still has pattern-a as parent expect( getParentSectionBlock( state, 'inner-block' ) ).toBe( 'pattern-a' ); } ); } ); } );