Skip to content

Commit

Permalink
Lodash: Refactor away from _.set() in core-data (#48784)
Browse files Browse the repository at this point in the history
* Lodash: Refactor away from _.set() in core-data

* Add graceful support and more tests
  • Loading branch information
tyxla authored Mar 13, 2023
1 parent 4feb24f commit c9b83b5
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 5 deletions.
5 changes: 3 additions & 2 deletions packages/core-data/src/queried-data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
*/
import createSelector from 'rememo';
import EquivalentKeyMap from 'equivalent-key-map';
import { set } from 'lodash';

/**
* Internal dependencies
*/
import getQueryParts from './get-query-parts';
import { setNestedValue } from '../utils';

/**
* Cache of state keys to EquivalentKeyMap where the inner map tracks queries
Expand Down Expand Up @@ -70,7 +70,8 @@ function getQueriedItemsUncached( state, query ) {
field.forEach( ( fieldName ) => {
value = value[ fieldName ];
} );
set( filteredItem, field, value );

setNestedValue( filteredItem, field, value );
}
} else {
// If expecting a complete item, validate that completeness, or
Expand Down
9 changes: 6 additions & 3 deletions packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* External dependencies
*/
import createSelector from 'rememo';
import { set } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -17,7 +16,11 @@ import deprecated from '@wordpress/deprecated';
import { STORE_NAME } from './name';
import { getQueriedItems } from './queried-data';
import { DEFAULT_ENTITY_KEY } from './entities';
import { getNormalizedCommaSeparable, isRawAttribute } from './utils';
import {
getNormalizedCommaSeparable,
isRawAttribute,
setNestedValue,
} from './utils';
import type * as ET from './entity-types';

// This is an incomplete, high-level approximation of the State type.
Expand Down Expand Up @@ -336,7 +339,7 @@ export const getEntityRecord = createSelector(
field.forEach( ( fieldName ) => {
value = value[ fieldName ];
} );
set( filteredItem, field, value );
setNestedValue( filteredItem, field, value );
}
return filteredItem as EntityRecord;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core-data/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as onSubKey } from './on-sub-key';
export { default as replaceAction } from './replace-action';
export { default as withWeakMapCache } from './with-weak-map-cache';
export { default as isRawAttribute } from './is-raw-attribute';
export { default as setNestedValue } from './set-nested-value';
37 changes: 37 additions & 0 deletions packages/core-data/src/utils/set-nested-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Sets the value at path of object.
* If a portion of path doesn’t exist, it’s created.
* Arrays are created for missing index properties while objects are created
* for all other missing properties.
*
* This function intentionally mutates the input object.
*
* Inspired by _.set().
*
* @see https://lodash.com/docs/4.17.15#set
*
* @param {Object} object Object to modify
* @param {Array} path Path of the property to set.
* @param {*} value Value to set.
*/
export default function setNestedValue( object, path, value ) {
if ( ! object || typeof object !== 'object' ) {
return object;
}

path.reduce( ( acc, key, idx ) => {
if ( acc[ key ] === undefined ) {
if ( Number.isInteger( path[ idx + 1 ] ) ) {
acc[ key ] = [];
} else {
acc[ key ] = {};
}
}
if ( idx === path.length - 1 ) {
acc[ key ] = value;
}
return acc[ key ];
}, object );

return object;
}
72 changes: 72 additions & 0 deletions packages/core-data/src/utils/test/set-nested-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Internal dependencies
*/
import setNestedValue from '../set-nested-value';

describe( 'setNestedValue', () => {
it( 'should return the same object unmodified if path is an empty array', () => {
const input = { x: 'y' };
const result = setNestedValue( input, [], 123 );

expect( result ).toBe( input );
expect( result ).toEqual( { x: 'y' } );
} );

it( 'should set values at deep level', () => {
const input = { x: { y: { z: 123 } } };
const result = setNestedValue( input, [ 'x', 'y', 'z' ], 456 );

expect( result ).toEqual( { x: { y: { z: 456 } } } );
} );

it( 'should create nested objects if necessary', () => {
const result = setNestedValue( {}, [ 'x', 'y', 'z' ], 123 );

expect( result ).toEqual( { x: { y: { z: 123 } } } );
} );

it( 'should create nested arrays when keys are numeric', () => {
const result = setNestedValue( {}, [ 'x', 0, 'z' ], 123 );

expect( result ).toEqual( { x: [ { z: 123 } ] } );
} );

it( 'should also work with arrays', () => {
const result = setNestedValue( [], [ 0, 1, 2 ], 123 );

expect( result ).toEqual( [ [ , [ , , 123 ] ] ] );
} );

it( 'should keep remaining properties unaffected', () => {
const input = { x: { y: { z: 123, z1: 'z1' }, y1: 'y1' }, x1: 'x1' };
const result = setNestedValue( input, [ 'x', 'y', 'z' ], 456 );

expect( result ).toEqual( {
x: { y: { z: 456, z1: 'z1' }, y1: 'y1' },
x1: 'x1',
} );
} );

it( 'should intentionally mutate the original object', () => {
const input = { x: 'y' };
const result = setNestedValue( input, [ 'x' ], 'z' );

expect( result ).toBe( input );
expect( result ).toEqual( { x: 'z' } );
} );

it.each( [
undefined,
null,
0,
5,
NaN,
Infinity,
'test',
false,
true,
Symbol( 'foo' ),
] )( 'should return the original input if it is %s', ( value ) => {
expect( setNestedValue( value, [ 'x' ], 123 ) ).toBe( value );
} );
} );

1 comment on commit c9b83b5

@github-actions
Copy link

@github-actions github-actions bot commented on c9b83b5 Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in c9b83b5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4405117455
📝 Reported issues:

Please sign in to comment.