@gechiui/block-editor
Version:
2,156 lines (1,981 loc) • 73.4 kB
JavaScript
/**
* External dependencies
*/
import { values, noop, omit } from 'lodash';
import deepFreeze from 'deep-freeze';
/**
* GeChiUI dependencies
*/
import {
registerBlockType,
unregisterBlockType,
createBlock,
} from '@gechiui/blocks';
/**
* Internal dependencies
*/
import {
hasSameKeys,
isUpdatingSameBlockAttribute,
blocks,
isTyping,
draggedBlocks,
isCaretWithinFormattedText,
selection,
initialPosition,
isMultiSelecting,
preferences,
blocksMode,
insertionPoint,
template,
blockListSettings,
lastBlockAttributesChange,
lastBlockInserted,
} from '../reducer';
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: {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
'chicken-child': {
clientId: 'chicken-child',
name: 'core/test-child-block',
isValid: true,
},
},
attributes: {
chicken: {},
'chicken-child': {
attr: true,
},
},
order: {
'': [ 'chicken' ],
chicken: [ 'chicken-child' ],
'chicken-child': [],
},
parents: {
chicken: '',
'chicken-child': 'chicken',
},
tree: {
'': {},
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 );
expect( omit( state, [ 'tree' ] ) ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-child-block',
isValid: true,
},
},
attributes: {
chicken: {},
[ newChildBlockId ]: {
attr: false,
attr2: 'perfect',
},
},
order: {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
},
parents: {
[ newChildBlockId ]: 'chicken',
chicken: '',
},
controlledInnerBlocks: {},
} );
expect( state.tree.chicken ).not.toBe(
existingState.tree.chicken
);
} );
it( 'can insert a child block', () => {
const existingState = deepFreeze( {
byClientId: {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
},
attributes: {
chicken: {},
},
order: {
'': [ 'chicken' ],
chicken: [],
},
parents: {
chicken: '',
},
tree: {
'': {
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 );
expect( omit( state, [ 'tree' ] ) ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-child-block',
isValid: true,
},
},
attributes: {
chicken: {},
[ newChildBlockId ]: {
attr: false,
attr2: 'perfect',
},
},
order: {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
},
parents: {
[ newChildBlockId ]: 'chicken',
chicken: '',
},
controlledInnerBlocks: {},
} );
expect( state.tree.chicken ).not.toBe(
existingState.tree.chicken
);
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.chicken
);
expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe(
state.tree[ newChildBlockId ]
);
expect( state.tree[ 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: {
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: {
chicken: {},
'chicken-child': {
attr: true,
},
'chicken-child-2': {
attr2: 'ok',
},
},
order: {
'': [ 'chicken' ],
chicken: [ 'chicken-child', 'chicken-child-2' ],
'chicken-child': [],
'chicken-child-2': [],
},
parents: {
chicken: '',
'chicken-child': 'chicken',
'chicken-child-2': 'chicken',
},
tree: {},
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 );
expect( omit( state, [ 'tree' ] ) ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: {
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: {
chicken: {},
[ newChildBlockId1 ]: {
attr: false,
attr2: 'perfect',
},
[ newChildBlockId2 ]: {
attr: true,
attr2: 'not-perfect',
},
[ newChildBlockId3 ]: {
attr2: 'hello',
},
},
order: {
'': [ 'chicken' ],
chicken: [
newChildBlockId1,
newChildBlockId2,
newChildBlockId3,
],
[ newChildBlockId1 ]: [],
[ newChildBlockId2 ]: [],
[ newChildBlockId3 ]: [],
},
parents: {
chicken: '',
[ newChildBlockId1 ]: 'chicken',
[ newChildBlockId2 ]: 'chicken',
[ newChildBlockId3 ]: 'chicken',
},
controlledInnerBlocks: {},
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.chicken
);
expect( state.tree.chicken.innerBlocks[ 0 ] ).toBe(
state.tree[ newChildBlockId1 ]
);
expect( state.tree.chicken.innerBlocks[ 1 ] ).toBe(
state.tree[ newChildBlockId2 ]
);
expect( state.tree.chicken.innerBlocks[ 2 ] ).toBe(
state.tree[ newChildBlockId3 ]
);
expect( state.tree[ 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: {
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: {
chicken: {},
'chicken-child': {},
'chicken-grand-child': {},
},
order: {
'': [ 'chicken' ],
chicken: [ 'chicken-child' ],
'chicken-child': [ 'chicken-grand-child' ],
'chicken-grand-child': [],
},
parents: {
chicken: '',
'chicken-child': 'chicken',
'chicken-grand-child': 'chicken-child',
},
tree: {
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 );
expect( omit( state, [ 'tree' ] ) ).toEqual( {
isPersistentChange: true,
isIgnoredChange: false,
byClientId: {
chicken: {
clientId: 'chicken',
name: 'core/test-parent-block',
isValid: true,
},
[ newChildBlockId ]: {
clientId: newChildBlockId,
name: 'core/test-block',
isValid: true,
},
},
attributes: {
chicken: {},
[ newChildBlockId ]: {},
},
order: {
'': [ 'chicken' ],
chicken: [ newChildBlockId ],
[ newChildBlockId ]: [],
},
parents: {
chicken: '',
[ newChildBlockId ]: 'chicken',
},
controlledInnerBlocks: {},
} );
// the block object of the parent should be updated
expect( state.tree.chicken ).not.toBe(
existingState.tree.chicken
);
} );
} );
it( 'should return empty byClientId, attributes, order by default', () => {
const state = blocks( undefined, {} );
expect( state ).toEqual( {
byClientId: {},
attributes: {},
order: {},
parents: {},
isPersistentChange: true,
isIgnoredChange: false,
tree: {},
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( Object.keys( state.byClientId ) ).toHaveLength( 1 );
expect( values( state.byClientId )[ 0 ].clientId ).toBe(
'bananas'
);
expect( state.order ).toEqual( {
'': [ 'bananas' ],
bananas: [],
} );
expect( state.tree.bananas ).toEqual( {
clientId: 'bananas',
innerBlocks: [],
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.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( Object.keys( state.byClientId ) ).toHaveLength( 2 );
expect( 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( Object.keys( state.byClientId ) ).toHaveLength( 2 );
expect( values( state.byClientId )[ 1 ].clientId ).toBe( 'ribs' );
expect( state.order ).toEqual( {
'': [ 'chicken', 'ribs' ],
chicken: [],
ribs: [],
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.chicken
);
expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe( state.tree.ribs );
expect( state.tree.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( Object.keys( state.byClientId ) ).toHaveLength( 1 );
expect( values( state.byClientId )[ 0 ].name ).toBe(
'core/freeform'
);
expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'wings' );
expect( state.order ).toEqual( {
'': [ 'wings' ],
wings: [],
} );
expect( state.parents ).toEqual( {
wings: '',
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.wings
);
expect( state.tree.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( Object.keys( state.byClientId ) ).toHaveLength( 0 );
expect( state.tree[ '' ].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( Object.keys( state.byClientId ) ).toHaveLength( 1 );
expect( state.order ).toEqual( {
'': [ 'wings' ],
wings: [],
} );
expect( state.parents ).toEqual( {
wings: '',
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.wings
);
expect( state.tree.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( state.order ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [ replacementBlock.clientId ],
[ replacementBlock.clientId ]: [],
} );
expect( state.parents ).toEqual( {
[ wrapperBlock.clientId ]: '',
[ replacementBlock.clientId ]: wrapperBlock.clientId,
} );
expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe(
state.tree[ replacementBlock.clientId ]
);
expect( state.tree[ 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( Object.keys( replacedState.byClientId ) ).toHaveLength( 1 );
expect( values( originalState.byClientId )[ 0 ].name ).toBe(
'core/test-block'
);
expect( values( replacedState.byClientId )[ 0 ].name ).toBe(
'core/freeform'
);
expect( values( replacedState.byClientId )[ 0 ].clientId ).toBe(
'chicken'
);
expect( replacedState.order ).toEqual( {
'': [ 'chicken' ],
chicken: [],
} );
expect( originalState.tree.chicken ).not.toBe(
replacedState.tree.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( replacedNestedState.order ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [ replacementNestedBlock.clientId ],
[ replacementNestedBlock.clientId ]: [],
} );
expect( originalNestedState.byClientId.chicken.name ).toBe(
'core/test-block'
);
expect( replacedNestedState.byClientId.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.chicken ).toEqual( {
clientId: 'chicken',
name: 'core/test-block',
isValid: true,
} );
expect( state.attributes.chicken ).toEqual( {
content: 'ribs',
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.chicken
);
expect( state.tree.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.chicken ).toEqual( {
clientId: 'chicken',
name: 'core/block',
isValid: false,
} );
expect( state.attributes.chicken ).toEqual( {
ref: 3,
} );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe(
state.tree.chicken
);
expect( state.tree.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[ '' ] ).toEqual( [ 'ribs', 'chicken' ] );
expect( state.tree[ '' ].innerBlocks[ 0 ] ).toBe( state.tree.ribs );
expect( state.tree[ '' ].innerBlocks[ 1 ] ).toBe(
state.tree.chicken
);
expect( state.tree.chicken ).toBe( original.tree.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( state.order ).toEqual( {
'': [ wrapperBlock.clientId ],
[ wrapperBlock.clientId ]: [
movedBlock.clientId,
siblingBlock.clientId,
],
[ movedBlock.clientId ]: [],
[ siblingBlock.clientId ]: [],
} );
expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 0 ] ).toBe(
state.tree[ movedBlock.clientId ]
);
expect( state.tree[ wrapperBlock.clientId ].innerBlocks[ 1 ] ).toBe(
state.tree[ siblingBlock.clientId ]
);
expect( state.tree[ movedBlock.clientId ] ).toBe(
original.tree[ 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[ '' ] ).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( 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[ '' ] ).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( 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[ '' ] ).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( 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[ '' ] ).toEqual( [ 'ribs' ] );
expect( state.order ).not.toHaveProperty( 'chicken' );
expect( state.parents ).toEqual( {
ribs: '',
} );
expect( state.byClientId ).toEqual( {
ribs: {
clientId: 'ribs',
name: 'core/test-block',
},
} );
expect( state.attributes ).toEqual( {
ribs: {},
} );
expect( state.tree[ '' ].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[ '' ] ).toEqual( [ 'ribs' ] );
expect( state.order ).not.toHaveProperty( 'chicken' );
expect( state.order ).not.toHaveProperty( 'veggies' );
expect( state.parents ).toEqual( {
ribs: '',
} );
expect( state.byClientId ).toEqual( {
ribs: {
clientId: 'ribs',
name: 'core/test-block',
},
} );
expect( 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( {} );
expect( state.order ).toEqual( {
'': [],
} );
expect( 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( Object.keys( state.byClientId ) ).toHaveLength( 3 );
expect( state.order[ '' ] ).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[ '' ] ).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[ '' ] ).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[ '' ] ).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[ '' ] ).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[ '' ] ).toEqual( [ 'chicken' ] );
expect( state.order.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.kumquat.updated ).toBe( true );
} );
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.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.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 );
} );
} );
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: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
original = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: false,
},
} );
const state = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
expect( state.isPersistentChange ).toBe( false );
} );
it( 'should flag an explicitly marked persistent change', () => {
let original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} )
);
original = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: false,
},
} );
original = blocks( original, {
type: 'UPDATE_BLOCK_ATTRIBUTES',
clientIds: [ 'kumquat' ],
attributes: {
updated: true,
},
} );
const state = blocks( original, {
type: 'MARK_LAST_CHANGE_AS_PERSISTENT',
} );
expect( state.isPersistentChange ).toBe( true );
} );
it( 'should retain reference for same state, same persistence', () => {
const original = deepFreeze(
blocks( undefined, {
type: 'RESET_BLOCKS',
blocks: [],
} )
);
const state = blocks( original, {
type: '__INERT__',
} );
expect( state ).toBe( original );
} );
} );
describe( 'isIgnoredChange', () => {
it( 'should consider received blocks as ignored change', () => {
const resetState = blocks( undefined, {
type: 'random action',
} );
const state = blocks( resetState, {
type: 'RECEIVE_BLOCKS',
blocks: [
{
clientId: 'kumquat',
attributes: {},
innerBlocks: [],
},
],
} );
expect( state.isIgnoredChange ).toBe( true );
} );
} );
describe( 'controlledInnerBlocks', () => {
it( 'should remove the content of the block if it switches from controlled to uncontrolled or opposite', () => {
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: 'SET_HAS_CONTROLLED_INNER_BLOCKS',
clientId: 'chicken',
hasControlledInnerBlocks: true,
} );
expect( state.controlledInnerBlocks.chicken ).toBe( true );
// The previous content of the block should be removed
expect( state.byClientId.child ).toBeUndefined();
expect( state.tree.child ).toBeUndefined();
expect( state.tree.chicken.innerBlocks ).toEqual( [] );
} );
} );
} );
} );
describe( 'insertionPoint', () => {
it( 'should default to null', () => {
const state = insertionPoint( undefined, {} );
expect( state ).toBe( null );
} );
it( 'should set insertion point', () => {
const state = insertionPoint( null, {
type: 'SHOW_INSERTION_POINT',
rootClientId: 'clientId1',
index: 0,
} );
expect( state ).toEqual( {
rootClientId: 'clientId1',
index: 0,
} );
} );
it( 'should clear the insertion point', () => {
const original = deepFreeze( {
rootClientId: 'clientId1',
index: 0,
} );
const state = insertionPoint( original, {
type: 'HIDE_INSERTION_POINT',
} );
expect( state ).toBe( null );
} );
} );
describe( 'isTyping()', () => {
it( 'should set the typing flag to true', () => {
const state = isTyping( false, {
type: 'START_TYPING',
} );
expect( state ).toBe( true );
} );
it( 'should set the typing flag to false', () => {
const state = isTyping( false, {
type: 'STOP_TYPING',
} );
expect( state ).toBe( false );
} );
} );
describe( 'draggedBlocks', () => {
it( 'should store the dragged client ids when a user starts dragging blocks', () => {
const clientIds = [ 'block-1', 'block-2', 'block-3' ];
const state = draggedBlocks( [], {
type: 'START_DRAGGING_BLOCKS',
clientIds,
} );
expect( state ).toBe( clientIds );
} );
it( 'should set the state to an empty array when a user stops dragging blocks', () => {
const previousState = [ 'block-1', 'block-2', 'block-3' ];
const state = draggedBlocks( previousState, {
type: 'STOP_