diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index a543c1e4f1d301..a144f1b1bf53a9 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -56,12 +56,17 @@ export function kebabCase( str ) { /** * Clones an object. + * Arrays are also cloned as arrays. * Non-object values are returned unchanged. * * @param {*} object Object to clone. * @return {*} Cloned object, or original literal non-object value. */ function cloneObject( object ) { + if ( Array.isArray( object ) ) { + return object.map( cloneObject ); + } + if ( object && typeof object === 'object' ) { return { ...Object.fromEntries( @@ -79,7 +84,7 @@ function cloneObject( object ) { /** * Immutably sets a value inside an object. Like `lodash#set`, but returning a * new object. Treats nullish initial values as empty objects. Clones any - * nested objects. + * nested objects. Supports arrays, too. * * @param {Object} object Object to set a value in. * @param {number|string|Array} path Path in the object to modify. @@ -92,7 +97,11 @@ export function setImmutably( object, path, value ) { normalizedPath.reduce( ( acc, key, i ) => { if ( acc[ key ] === undefined ) { - acc[ key ] = {}; + if ( Number.isInteger( path[ i + 1 ] ) ) { + acc[ key ] = []; + } else { + acc[ key ] = {}; + } } if ( i === normalizedPath.length - 1 ) { acc[ key ] = value; diff --git a/packages/block-editor/src/utils/test/object.js b/packages/block-editor/src/utils/test/object.js index def7e5e9c8f057..87f01375df311d 100644 --- a/packages/block-editor/src/utils/test/object.js +++ b/packages/block-editor/src/utils/test/object.js @@ -150,6 +150,22 @@ describe( 'setImmutably', () => { expect( result ).toEqual( { test: 2 } ); } ); + it( 'handles first level arrays properly', () => { + const result = setImmutably( [ 5 ], 0, 6 ); + + expect( result ).toEqual( [ 6 ] ); + } ); + + it( 'handles nested arrays properly', () => { + const result = setImmutably( + [ [ 'foo', [ 'bar' ] ] ], + [ 0, 1, 0 ], + 'baz' + ); + + expect( result ).toEqual( [ [ 'foo', [ 'baz' ] ] ] ); + } ); + describe( 'with array notation access', () => { it( 'assigns values at deeper levels', () => { const result = setImmutably( {}, [ 'foo', 'bar', 'baz' ], 5 ); @@ -236,5 +252,25 @@ describe( 'setImmutably', () => { expect( result.foo.bar ).not.toBe( input.foo.bar ); expect( result.foo.bar.baz ).not.toBe( input.foo.bar.baz ); } ); + + it( 'clones arrays at the first level', () => { + const input = []; + const result = setImmutably( input, 0, 1 ); + + expect( result ).not.toBe( input ); + } ); + + it( 'clones arrays at deeper levels', () => { + const input = [ [ [ [ 'foo', [ 'bar' ] ] ] ] ]; + const result = setImmutably( input, [ 0, 0, 0, 1, 0 ], 'baz' ); + + expect( result ).not.toBe( input ); + expect( result[ 0 ] ).not.toBe( input[ 0 ] ); + expect( result[ 0 ][ 0 ] ).not.toBe( input[ 0 ][ 0 ] ); + expect( result[ 0 ][ 0 ][ 0 ] ).not.toBe( input[ 0 ][ 0 ][ 0 ] ); + expect( result[ 0 ][ 0 ][ 0 ][ 1 ] ).not.toBe( + input[ 0 ][ 0 ][ 0 ][ 1 ] + ); + } ); } ); } );