@wordpress/block-editor
Version:
2,134 lines (1,974 loc) • 108 kB
JavaScript
/**
* External dependencies
*/
import deepFreeze from 'deep-freeze';
/**
* WordPress dependencies
*/
import {
registerBlockType,
unregisterBlockType,
createBlock,
privateApis,
} from '@wordpress/blocks';
import { combineReducers } from '@wordpress/data';
/**
* Internal dependencies
*/
import {
hasSameKeys,
isUpdatingSameBlockAttribute,
blocks,
isBlockInterfaceHidden,
isTyping,
isDragging,
draggedBlocks,
selection,
initialPosition,
isMultiSelecting,
preferences,
blocksMode,
insertionCue,
insertionPoint,
template,
blockListSettings,
settings,
lastBlockAttributesChange,
lastBlockInserted,
blockEditingModes,
openedBlockSettingsMenu,
expandedBlock,
zoomLevel,
withDerivedBlockEditingModes,
} from '../reducer';
import { unlock } from '../../lock-unlock';
import { sectionRootClientIdKey } from '.././private-keys';
const { isContentBlock } = unlock( privateApis );
jest.mock( '@wordpress/data/src/select', () => {
const actualSelect = jest.requireActual( '@wordpress/data/src/select' );
return {
select: jest.fn( ( ...args ) => actualSelect.select( ...args ) ),
};
} );
jest.mock( '@wordpress/blocks/src/api/utils', () => {
return {
...jest.requireActual( '@wordpress/blocks/src/api/utils' ),
isContentBlock: jest.fn(),
};
} );
const noop = () => {};
describe( 'state', () => {
describe( 'hasSameKeys()', () => {
it( 'returns false if two objects do not have the same keys', () => {
const a = { foo: 10 };
const b = { bar: 10 };
expect( hasSameKeys( a, b ) ).toBe( false );
} );
it( 'returns false if two objects have the same keys', () => {
const a = { foo: 10 };
const b = { foo: 20 };
expect( hasSameKeys( a, b ) ).toBe( true );
} );
} );
describe( 'isUpdatingSameBlockAttribute()', () => {
it( 'should return false if not updating block attributes', () => {
const action = {
type: 'SELECT_BLOCK',
clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189',
};
const previousAction = {
type: 'SELECT_BLOCK',
clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189',
};
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( false );
} );
it( 'should return false if last action was not updating block attributes', () => {
const action = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 10,
},
};
const previousAction = {
type: 'SELECT_BLOCK',
clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189',
};
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( false );
} );
it( 'should return false if not updating the same block', () => {
const action = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 10,
},
};
const previousAction = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ],
attributes: {
foo: 20,
},
};
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( false );
} );
it( 'should return false if not updating the same block attributes', () => {
const action = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 10,
},
};
const previousAction = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
bar: 20,
},
};
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( false );
} );
it( 'should return false if no previous action', () => {
const action = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 10,
},
};
const previousAction = undefined;
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( false );
} );
it( 'should return true if updating the same block attributes', () => {
const action = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 10,
},
};
const previousAction = {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ '9db792c6-a25a-495d-adbd-97d56a4c4189' ],
attributes: {
foo: 20,
},
};
expect(
isUpdatingSameBlockAttribute( action, previousAction )
).toBe( true );
} );
} );
describe( 'blocks()', () => {
beforeAll( () => {
registerBlockType( 'core/test-block', {
save: noop,
edit: noop,
category: 'text',
title: 'test block',
} );
} );
afterAll( () => {
unregisterBlockType( 'core/test-block' );
} );
describe( 'replace inner blocks', () => {
beforeAll( () => {
registerBlockType( 'core/test-parent-block', {
save: noop,
edit: noop,
category: 'text',
title: 'test parent block',
} );
registerBlockType( 'core/test-child-block', {
save: noop,
edit: noop,
category: 'text',
title: 'test child block 1',
attributes: {
attr: {
type: 'boolean',
},
attr2: {
type: 'string',
},
},
} );
} );
afterAll( () => {
unregisterBlockType( 'core/test-parent-block' );
unregisterBlockType( 'core/test-child-block' );
} );
it( 'can replace a child block', () => {
const existingState = deepFreeze( {
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
'chicken-child': {
clientId: 'chicken-child',
name: 'core/test-child-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
'chicken-child': {
attr: true,
},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ 'chicken-child' ],
'chicken-child': [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
'chicken-child': 'chicken',
} )
),
tree: new Map(
Object.entries( {
'': {},
chicken: {},
'chicken-child': {},
} )
),
controlledInnerBlocks: {},
} );
const newChildBlock = createBlock( 'core/test-child-block', {
attr: false,
attr2: 'perfect',
} );
const { clientId: newChildBlockId } = newChildBlock;
const action = {
type: 'REPLACE_INNER_BLOCKS',
rootClientId: 'chicken',
blocks: [ newChildBlock ],
};
const state = blocks( existingState, action );
const { tree, ...restState } = state;
expect( restState ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-child-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
[ newChildBlockId ]: {
attr: false,
attr2: 'perfect',
},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
} )
),
parents: new Map(
Object.entries( {
[ newChildBlockId ]: 'chicken',
chicken: '',
} )
),
controlledInnerBlocks: {},
} );
expect( state.tree.get( 'chicken' ) ).not.toBe(
existingState.tree.get( 'chicken' )
);
} );
it( 'can insert a child block', () => {
const existingState = deepFreeze( {
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
} )
),
tree: new Map(
Object.entries( {
'': {
innerBlocks: [],
},
chicken: {},
} )
),
controlledInnerBlocks: {},
} );
const newChildBlock = createBlock( 'core/test-child-block', {
attr: false,
attr2: 'perfect',
} );
const { clientId: newChildBlockId } = newChildBlock;
const action = {
type: 'REPLACE_INNER_BLOCKS',
rootClientId: 'chicken',
blocks: [ newChildBlock ],
};
const state = blocks( existingState, action );
const { tree, ...restState } = state;
expect( restState ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-child-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
[ newChildBlockId ]: {
attr: false,
attr2: 'perfect',
},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
} )
),
parents: new Map(
Object.entries( {
[ newChildBlockId ]: 'chicken',
chicken: '',
} )
),
controlledInnerBlocks: {},
} );
expect( state.tree.get( 'chicken' ) ).not.toBe(
existingState.tree.get( 'chicken' )
);
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( 'chicken' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( newChildBlockId )
);
expect( state.tree.get( newChildBlockId ) ).toEqual( {
clientId: newChildBlockId,
innerBlocks: [],
isValid: true,
name: 'core/test-child-block',
attributes: {
attr: false,
attr2: 'perfect',
},
} );
} );
it( 'can replace multiple child blocks', () => {
const existingState = deepFreeze( {
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
'chicken-child': {
clientId: 'chicken-child',
name: 'core/test-child-block',
isValid: true,
},
'chicken-child-2': {
clientId: 'chicken-child',
name: 'core/test-child-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
'chicken-child': {
attr: true,
},
'chicken-child-2': {
attr2: 'ok',
},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ 'chicken-child', 'chicken-child-2' ],
'chicken-child': [],
'chicken-child-2': [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
'chicken-child': 'chicken',
'chicken-child-2': 'chicken',
} )
),
tree: new Map(),
controlledInnerBlocks: {},
} );
const newChildBlock1 = createBlock( 'core/test-child-block', {
attr: false,
attr2: 'perfect',
} );
const newChildBlock2 = createBlock( 'core/test-child-block', {
attr: true,
attr2: 'not-perfect',
} );
const newChildBlock3 = createBlock( 'core/test-child-block', {
attr2: 'hello',
} );
const { clientId: newChildBlockId1 } = newChildBlock1;
const { clientId: newChildBlockId2 } = newChildBlock2;
const { clientId: newChildBlockId3 } = newChildBlock3;
const action = {
type: 'REPLACE_INNER_BLOCKS',
rootClientId: 'chicken',
blocks: [ newChildBlock1, newChildBlock2, newChildBlock3 ],
};
const state = blocks( existingState, action );
const { tree, ...restState } = state;
expect( restState ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId1 ]: {
clientId: newChildBlockId1,
name: 'core/test-child-block',
isValid: true,
},
[ newChildBlockId2 ]: {
clientId: newChildBlockId2,
name: 'core/test-child-block',
isValid: true,
},
[ newChildBlockId3 ]: {
clientId: newChildBlockId3,
name: 'core/test-child-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
[ newChildBlockId1 ]: {
attr: false,
attr2: 'perfect',
},
[ newChildBlockId2 ]: {
attr: true,
attr2: 'not-perfect',
},
[ newChildBlockId3 ]: {
attr2: 'hello',
},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [
newChildBlockId1,
newChildBlockId2,
newChildBlockId3,
],
[ newChildBlockId1 ]: [],
[ newChildBlockId2 ]: [],
[ newChildBlockId3 ]: [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
[ newChildBlockId1 ]: 'chicken',
[ newChildBlockId2 ]: 'chicken',
[ newChildBlockId3 ]: 'chicken',
} )
),
controlledInnerBlocks: {},
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( 'chicken' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( newChildBlockId1 )
);
expect( state.tree.get( 'chicken' ).innerBlocks[ 1 ] ).toBe(
state.tree.get( newChildBlockId2 )
);
expect( state.tree.get( 'chicken' ).innerBlocks[ 2 ] ).toBe(
state.tree.get( newChildBlockId3 )
);
expect( state.tree.get( newChildBlockId1 ) ).toEqual( {
innerBlocks: [],
clientId: newChildBlockId1,
name: 'core/test-child-block',
isValid: true,
attributes: {
attr: false,
attr2: 'perfect',
},
} );
} );
it( 'can replace a child block that has other children', () => {
const existingState = deepFreeze( {
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
'chicken-child': {
clientId: 'chicken-child',
name: 'core/test-child-block',
isValid: true,
},
'chicken-grand-child': {
clientId: 'chicken-child',
name: 'core/test-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
'chicken-child': {},
'chicken-grand-child': {},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ 'chicken-child' ],
'chicken-child': [ 'chicken-grand-child' ],
'chicken-grand-child': [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
'chicken-child': 'chicken',
'chicken-grand-child': 'chicken-child',
} )
),
tree: new Map(
Object.entries( {
chicken: {},
} )
),
controlledInnerBlocks: {},
} );
const newChildBlock = createBlock( 'core/test-block' );
const { clientId: newChildBlockId } = newChildBlock;
const action = {
type: 'REPLACE_INNER_BLOCKS',
rootClientId: 'chicken',
blocks: [ newChildBlock ],
};
const state = blocks( existingState, action );
const { tree, ...restState } = state;
expect( restState ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: new Map(
Object.entries( {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-block',
isValid: true,
},
} )
),
attributes: new Map(
Object.entries( {
chicken: {},
[ newChildBlockId ]: {},
} )
),
order: new Map(
Object.entries( {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
} )
),
parents: new Map(
Object.entries( {
chicken: '',
[ newChildBlockId ]: 'chicken',
} )
),
controlledInnerBlocks: {},
} );
// The block object of the parent should be updated.
expect( state.tree.get( 'chicken' ) ).not.toBe(
existingState.tree.get( 'chicken' )
);
} );
} );
it( 'should return empty byClientId, attributes, order by default', () => {
const state = blocks( undefined, {} );
expect( state ).toEqual( {
byClientId: new Map(),
attributes: new Map(),
order: new Map(),
parents: new Map(),
isPersistentChange: true,
isIgnoredChange: false,
tree: new Map(),
controlledInnerBlocks: {},
} );
} );
it( 'should key by reset blocks clientId', () => {
[ undefined, blocks( undefined, {} ) ].forEach( ( original ) => {
const state = blocks( original, {
type: 'RESET_BLOCKS',
blocks: [ { clientId: 'bananas', innerBlocks: [] } ],
} );
expect( state.byClientId.size ).toBe( 1 );
expect( state.byClientId.get( 'bananas' ).clientId ).toBe(
'bananas'
);
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ 'bananas' ],
bananas: [],
} );
expect( state.tree.get( 'bananas' ) ).toEqual( {
clientId: 'bananas',
innerBlocks: [],
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'bananas' )
);
} );
} );
it( 'should key by reset blocks clientId, including inner blocks', () => {
const original = blocks( undefined, {} );
const state = blocks( original, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'bananas',
innerBlocks: [
{ clientId: 'apples', innerBlocks: [] },
],
},
],
} );
expect( state.byClientId.size ).toBe( 2 );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ 'bananas' ],
apples: [],
bananas: [ 'apples' ],
} );
} );
it( 'should insert block', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'INSERT_BLOCKS',
blocks: [
{
clientId: 'ribs',
name: 'core/freeform',
innerBlocks: [],
},
],
} );
expect( state.byClientId.size ).toBe( 2 );
expect( state.byClientId.get( 'ribs' ).clientId ).toBe( 'ribs' );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ 'chicken', 'ribs' ],
chicken: [],
ribs: [],
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( '' ).innerBlocks[ 1 ] ).toBe(
state.tree.get( 'ribs' )
);
expect( state.tree.get( 'chicken' ) ).toEqual( {
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
} );
} );
it( 'should replace the block', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'REPLACE_BLOCKS',
clientIds: [ 'chicken' ],
blocks: [
{
clientId: 'wings',
name: 'core/freeform',
innerBlocks: [],
},
],
} );
expect( state.byClientId.size ).toBe( 1 );
expect( state.byClientId.get( 'wings' ).name ).toBe(
'core/freeform'
);
expect( state.byClientId.get( 'wings' ).clientId ).toBe( 'wings' );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ 'wings' ],
wings: [],
} );
expect( Object.fromEntries( state.parents ) ).toEqual( {
wings: '',
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'wings' )
);
expect( state.tree.get( 'wings' ) ).toEqual( {
clientId: 'wings',
name: 'core/freeform',
innerBlocks: [],
} );
} );
it( 'Replacing the block with an empty list should remove it', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'REPLACE_BLOCKS',
clientIds: [ 'chicken' ],
blocks: [],
} );
expect( state.byClientId.size ).toBe( 0 );
expect( state.tree.get( '' ).innerBlocks ).toHaveLength( 0 );
} );
it( 'should replace the block and remove references to its inner blocks', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [
{
clientId: 'child',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
},
],
} );
const state = blocks( original, {
type: 'REPLACE_BLOCKS',
clientIds: [ 'chicken' ],
blocks: [
{
clientId: 'wings',
name: 'core/freeform',
innerBlocks: [],
},
],
} );
expect( state.byClientId.size ).toBe( 1 );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ 'wings' ],
wings: [],
} );
expect( Object.fromEntries( state.parents ) ).toEqual( {
wings: '',
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'wings' )
);
expect( state.tree.get( 'wings' ) ).toEqual( {
clientId: 'wings',
name: 'core/freeform',
innerBlocks: [],
} );
} );
it( 'should replace the nested block', () => {
const nestedBlock = createBlock( 'core/test-block' );
const wrapperBlock = createBlock( 'core/test-block', {}, [
nestedBlock,
] );
const replacementBlock = createBlock( 'core/test-block' );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const state = blocks( original, {
type: 'REPLACE_BLOCKS',
clientIds: [ nestedBlock.clientId ],
blocks: [ replacementBlock ],
} );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [ replacementBlock.clientId ],
[ replacementBlock.clientId ]: [],
} );
expect( Object.fromEntries( state.parents ) ).toEqual( {
[ wrapperBlock.clientId ]: '',
[ replacementBlock.clientId ]: wrapperBlock.clientId,
} );
expect(
state.tree.get( wrapperBlock.clientId ).innerBlocks[ 0 ]
).toBe( state.tree.get( replacementBlock.clientId ) );
expect( state.tree.get( replacementBlock.clientId ) ).toEqual( {
clientId: replacementBlock.clientId,
name: 'core/test-block',
innerBlocks: [],
attributes: {},
isValid: true,
} );
} );
it( 'should replace the block even if the new block clientId is the same', () => {
const originalState = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const replacedState = blocks( originalState, {
type: 'REPLACE_BLOCKS',
clientIds: [ 'chicken' ],
blocks: [
{
clientId: 'chicken',
name: 'core/freeform',
innerBlocks: [],
},
],
} );
expect( replacedState.byClientId.size ).toBe( 1 );
expect( originalState.byClientId.get( 'chicken' ).name ).toBe(
'core/test-block'
);
expect( replacedState.byClientId.get( 'chicken' ).name ).toBe(
'core/freeform'
);
expect( replacedState.byClientId.get( 'chicken' ).clientId ).toBe(
'chicken'
);
expect( Object.fromEntries( replacedState.order ) ).toEqual( {
'': [ 'chicken' ],
chicken: [],
} );
expect( originalState.tree.get( 'chicken' ) ).not.toBe(
replacedState.tree.get( 'chicken' )
);
const nestedBlock = {
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
};
const wrapperBlock = createBlock( 'core/test-block', {}, [
nestedBlock,
] );
const replacementNestedBlock = {
clientId: 'chicken',
name: 'core/freeform',
attributes: {},
innerBlocks: [],
};
const originalNestedState = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const replacedNestedState = blocks( originalNestedState, {
type: 'REPLACE_BLOCKS',
clientIds: [ nestedBlock.clientId ],
blocks: [ replacementNestedBlock ],
} );
expect( Object.fromEntries( replacedNestedState.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ],
[ replacementNestedBlock.clientId ]: [],
} );
expect( originalNestedState.byClientId.get( 'chicken' ).name ).toBe(
'core/test-block'
);
expect( replacedNestedState.byClientId.get( 'chicken' ).name ).toBe(
'core/freeform'
);
} );
it( 'should update the block', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
isValid: false,
innerBlocks: [],
},
],
} );
const state = blocks( deepFreeze( original ), {
type: 'UPDATE_BLOCK',
clientId: 'chicken',
updates: {
attributes: { content: 'ribs' },
isValid: true,
},
} );
expect( state.byClientId.get( 'chicken' ) ).toEqual( {
clientId: 'chicken',
name: 'core/test-block',
isValid: true,
} );
expect( state.attributes.get( 'chicken' ) ).toEqual( {
content: 'ribs',
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( 'chicken' ) ).toEqual( {
clientId: 'chicken',
name: 'core/test-block',
innerBlocks: [],
attributes: {
content: 'ribs',
},
isValid: true,
} );
} );
it( 'should update the reusable block reference if the temporary id is swapped', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/block',
attributes: {
ref: 'random-clientId',
},
isValid: false,
innerBlocks: [],
},
],
} );
const state = blocks( deepFreeze( original ), {
type: 'SAVE_REUSABLE_BLOCK_SUCCESS',
id: 'random-clientId',
updatedId: 3,
} );
expect( state.byClientId.get( 'chicken' ) ).toEqual( {
clientId: 'chicken',
name: 'core/block',
isValid: false,
} );
expect( state.attributes.get( 'chicken' ) ).toEqual( {
ref: 3,
} );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( 'chicken' ) ).toEqual( {
clientId: 'chicken',
name: 'core/block',
isValid: false,
innerBlocks: [],
attributes: {
ref: 3,
},
} );
} );
it( 'should move the block up', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_UP',
clientIds: [ 'ribs' ],
} );
expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'chicken' ] );
expect( state.tree.get( '' ).innerBlocks[ 0 ] ).toBe(
state.tree.get( 'ribs' )
);
expect( state.tree.get( '' ).innerBlocks[ 1 ] ).toBe(
state.tree.get( 'chicken' )
);
expect( state.tree.get( 'chicken' ) ).toBe(
original.tree.get( 'chicken' )
);
} );
it( 'should move the nested block up', () => {
const movedBlock = createBlock( 'core/test-block' );
const siblingBlock = createBlock( 'core/test-block' );
const wrapperBlock = createBlock( 'core/test-block', {}, [
siblingBlock,
movedBlock,
] );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_UP',
clientIds: [ movedBlock.clientId ],
rootClientId: wrapperBlock.clientId,
} );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [
movedBlock.clientId,
siblingBlock.clientId,
],
[ movedBlock.clientId ]: [],
[ siblingBlock.clientId ]: [],
} );
expect(
state.tree.get( wrapperBlock.clientId ).innerBlocks[ 0 ]
).toBe( state.tree.get( movedBlock.clientId ) );
expect(
state.tree.get( wrapperBlock.clientId ).innerBlocks[ 1 ]
).toBe( state.tree.get( siblingBlock.clientId ) );
expect( state.tree.get( movedBlock.clientId ) ).toBe(
original.tree.get( movedBlock.clientId )
);
} );
it( 'should move multiple blocks up', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_UP',
clientIds: [ 'ribs', 'veggies' ],
} );
expect( state.order.get( '' ) ).toEqual( [
'ribs',
'veggies',
'chicken',
] );
} );
it( 'should move multiple nested blocks up', () => {
const movedBlockA = createBlock( 'core/test-block' );
const movedBlockB = createBlock( 'core/test-block' );
const siblingBlock = createBlock( 'core/test-block' );
const wrapperBlock = createBlock( 'core/test-block', {}, [
siblingBlock,
movedBlockA,
movedBlockB,
] );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_UP',
clientIds: [ movedBlockA.clientId, movedBlockB.clientId ],
rootClientId: wrapperBlock.clientId,
} );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [
movedBlockA.clientId,
movedBlockB.clientId,
siblingBlock.clientId,
],
[ movedBlockA.clientId ]: [],
[ movedBlockB.clientId ]: [],
[ siblingBlock.clientId ]: [],
} );
} );
it( 'should not move the first block up', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_UP',
clientIds: [ 'chicken' ],
} );
expect( state.order ).toBe( original.order );
} );
it( 'should move the block down', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_DOWN',
clientIds: [ 'chicken' ],
} );
expect( state.order.get( '' ) ).toEqual( [ 'ribs', 'chicken' ] );
} );
it( 'should move the nested block down', () => {
const movedBlock = createBlock( 'core/test-block' );
const siblingBlock = createBlock( 'core/test-block' );
const wrapperBlock = createBlock( 'core/test-block', {}, [
movedBlock,
siblingBlock,
] );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_DOWN',
clientIds: [ movedBlock.clientId ],
rootClientId: wrapperBlock.clientId,
} );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [
siblingBlock.clientId,
movedBlock.clientId,
],
[ movedBlock.clientId ]: [],
[ siblingBlock.clientId ]: [],
} );
} );
it( 'should move multiple blocks down', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_DOWN',
clientIds: [ 'chicken', 'ribs' ],
} );
expect( state.order.get( '' ) ).toEqual( [
'veggies',
'chicken',
'ribs',
] );
} );
it( 'should move multiple nested blocks down', () => {
const movedBlockA = createBlock( 'core/test-block' );
const movedBlockB = createBlock( 'core/test-block' );
const siblingBlock = createBlock( 'core/test-block' );
const wrapperBlock = createBlock( 'core/test-block', {}, [
movedBlockA,
movedBlockB,
siblingBlock,
] );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ wrapperBlock ],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_DOWN',
clientIds: [ movedBlockA.clientId, movedBlockB.clientId ],
rootClientId: wrapperBlock.clientId,
} );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [
siblingBlock.clientId,
movedBlockA.clientId,
movedBlockB.clientId,
],
[ movedBlockA.clientId ]: [],
[ movedBlockB.clientId ]: [],
[ siblingBlock.clientId ]: [],
} );
} );
it( 'should not move the last block down', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_DOWN',
clientIds: [ 'ribs' ],
} );
expect( state.order ).toBe( original.order );
} );
it( 'should remove the block', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'REMOVE_BLOCKS',
clientIds: [ 'chicken' ],
} );
expect( state.order.get( '' ) ).toEqual( [ 'ribs' ] );
expect( Object.fromEntries( state.order ) ).not.toHaveProperty(
'chicken'
);
expect( Object.fromEntries( state.parents ) ).toEqual( {
ribs: '',
} );
expect( Object.fromEntries( state.byClientId ) ).toEqual( {
ribs: {
clientId: 'ribs',
name: 'core/test-block',
},
} );
expect( Object.fromEntries( state.attributes ) ).toEqual( {
ribs: {},
} );
expect( state.tree.get( '' ).innerBlocks ).toHaveLength( 1 );
} );
it( 'should remove multiple blocks', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'REMOVE_BLOCKS',
clientIds: [ 'chicken', 'veggies' ],
} );
expect( state.order.get( '' ) ).toEqual( [ 'ribs' ] );
expect( Object.fromEntries( state.order ) ).not.toHaveProperty(
'chicken'
);
expect( Object.fromEntries( state.order ) ).not.toHaveProperty(
'veggies'
);
expect( Object.fromEntries( state.parents ) ).toEqual( {
ribs: '',
} );
expect( Object.fromEntries( state.byClientId ) ).toEqual( {
ribs: {
clientId: 'ribs',
name: 'core/test-block',
},
} );
expect( Object.fromEntries( state.attributes ) ).toEqual( {
ribs: {},
} );
} );
it( 'should cascade remove to include inner blocks', () => {
const block = createBlock( 'core/test-block', {}, [
createBlock( 'core/test-block', {}, [
createBlock( 'core/test-block' ),
] ),
] );
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [ block ],
} );
const state = blocks( original, {
type: 'REMOVE_BLOCKS',
clientIds: [ block.clientId ],
} );
expect( state.byClientId ).toEqual( new Map() );
expect( Object.fromEntries( state.order ) ).toEqual( {
'': [],
} );
expect( Object.fromEntries( state.parents ) ).toEqual( {} );
} );
it( 'should insert at the specified index', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'loquat',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'INSERT_BLOCKS',
index: 1,
blocks: [
{
clientId: 'persimmon',
name: 'core/freeform',
innerBlocks: [],
},
],
} );
expect( state.byClientId.size ).toBe( 3 );
expect( state.order.get( '' ) ).toEqual( [
'kumquat',
'persimmon',
'loquat',
] );
} );
it( 'should move block to lower index', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_TO_POSITION',
clientIds: [ 'ribs' ],
index: 0,
} );
expect( state.order.get( '' ) ).toEqual( [
'ribs',
'chicken',
'veggies',
] );
} );
it( 'should move block to higher index', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_TO_POSITION',
clientIds: [ 'ribs' ],
index: 2,
} );
expect( state.order.get( '' ) ).toEqual( [
'chicken',
'veggies',
'ribs',
] );
} );
it( 'should not move block if passed same index', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_TO_POSITION',
clientIds: [ 'ribs' ],
index: 1,
} );
expect( state.order.get( '' ) ).toEqual( [
'chicken',
'ribs',
'veggies',
] );
} );
it( 'should move multiple blocks', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_TO_POSITION',
clientIds: [ 'ribs', 'veggies' ],
index: 0,
} );
expect( state.order.get( '' ) ).toEqual( [
'ribs',
'veggies',
'chicken',
] );
} );
it( 'should move multiple blocks to different parent', () => {
const original = blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'chicken',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'ribs',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
{
clientId: 'veggies',
name: 'core/test-block',
attributes: {},
innerBlocks: [],
},
],
} );
const state = blocks( original, {
type: 'MOVE_BLOCKS_TO_POSITION',
clientIds: [ 'ribs', 'veggies' ],
fromRootClientId: '',
toRootClientId: 'chicken',
index: 0,
} );
expect( state.order.get( '' ) ).toEqual( [ 'chicken' ] );
expect( state.order.get( 'chicken' ) ).toEqual( [
'ribs',
'veggies',
] );
} );
describe( 'blocks', () => {
describe( 'byClientId', () => {
it( 'should ignore updates to non-existent block', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.byClientId ).toBe( original.byClientId );
} );
it( 'should return with same reference if no changes in updates', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {
updated: true,
},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.byClientId ).toBe( state.byClientId );
} );
} );
describe( 'attributes', () => {
it( 'should return with attribute block updates', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.attributes.get( 'kumquat' ).updated ).toBe(
true
);
} );
it( 'should not updated equal attributes', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
const updatedState = blocks( state, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.attributes.get( 'kumquat' ) ).toBe(
updatedState.attributes.get( 'kumquat' )
);
} );
it( 'should return with attribute block updates when attributes are unique by block', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
kumquat: { updated: true },
},
uniqueByBlock: true,
} );
expect( state.attributes.get( 'kumquat' ).updated ).toBe(
true
);
} );
it( 'should accumulate attribute block updates', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {
updated: true,
},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
moreUpdated: true,
},
} );
expect( state.attributes.get( 'kumquat' ) ).toEqual( {
updated: true,
moreUpdated: true,
} );
} );
it( 'should ignore updates to non-existent block', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.attributes ).toBe( original.attributes );
} );
it( 'should return with same reference if no changes in updates', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {
updated: true,
},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.attributes ).toBe( state.attributes );
} );
it( 'should handle undefined attributes', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
} );
expect( state.attributes.get( 'kumquat' ) ).toEqual( {} );
} );
} );
describe( 'isPersistentChange', () => {
it( 'should default a changing state to true', () => {
const state = deepFreeze( blocks( undefined, {} ) );
expect( state.isPersistentChange ).toBe( true );
} );
it( 'should consider any non-exempt block change as persistent', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [],
} )
);
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.isPersistentChange ).toBe( true );
} );
it( 'should consider any non-exempt block change as persistent across unchanging actions', () => {
let original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
original = blocks( original, {
type: 'NOOP',
} );
original = blocks( original, {
// While RECEIVE_BLOCKS changes state, it's considered
// as ignored, confirmed by this test.
type: 'RECEIVE_BLOCKS',
blocks: [],
} );
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: false,
},
} );
expect( state.isPersistentChange ).toBe( true );
} );
it( 'should consider same block attribute update as exempt', () => {
let original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [