Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core-data: Move setNestedValue to block editor package #52409

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/block-editor/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as globalStyles from './components/global-styles';
import { ExperimentalBlockEditorProvider } from './components/provider';
import { lock } from './lock-unlock';
import { getRichTextValues } from './components/rich-text/content';
import { kebabCase } from './utils/object';
import { kebabCase, setNestedValue } from './utils/object';
import ResizableBoxPopover from './components/resizable-box-popover';
import { ComposedPrivateInserter as PrivateInserter } from './components/inserter';
import { PrivateListView } from './components/list-view';
Expand All @@ -29,6 +29,7 @@ lock( privateApis, {
ExperimentalBlockEditorProvider,
getRichTextValues,
kebabCase,
setNestedValue,
PrivateInserter,
PrivateListView,
ResizableBoxPopover,
Expand Down
38 changes: 38 additions & 0 deletions packages/block-editor/src/utils/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,41 @@ export function setImmutably( object, path, value ) {

return newObject;
}

/**
* 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 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;
}
70 changes: 69 additions & 1 deletion packages/block-editor/src/utils/test/object.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Internal dependencies
*/
import { kebabCase, setImmutably } from '../object';
import { kebabCase, setImmutably, setNestedValue } from '../object';

describe( 'kebabCase', () => {
it( 'separates lowercase letters, followed by uppercase letters', () => {
Expand Down Expand Up @@ -274,3 +274,71 @@ describe( 'setImmutably', () => {
} );
} );
} );

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 );
} );
} );
9 changes: 8 additions & 1 deletion packages/core-data/src/queried-data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
import createSelector from 'rememo';
import EquivalentKeyMap from 'equivalent-key-map';

/**
* WordPress dependencies
*/
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';

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

const { setNestedValue } = unlock( blockEditorPrivateApis );

/**
* Cache of state keys to EquivalentKeyMap where the inner map tracks queries
Expand Down
10 changes: 5 additions & 5 deletions packages/core-data/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import createSelector from 'rememo';
/**
* WordPress dependencies
*/
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import { createRegistrySelector } from '@wordpress/data';
import { addQueryArgs } from '@wordpress/url';
import deprecated from '@wordpress/deprecated';
Expand All @@ -16,13 +17,12 @@ import deprecated from '@wordpress/deprecated';
import { STORE_NAME } from './name';
import { getQueriedItems } from './queried-data';
import { DEFAULT_ENTITY_KEY } from './entities';
import {
getNormalizedCommaSeparable,
isRawAttribute,
setNestedValue,
} from './utils';
import { getNormalizedCommaSeparable, isRawAttribute } from './utils';
import type * as ET from './entity-types';
import { getUndoEdits, getRedoEdits } from './private-selectors';
import { unlock } from './private-apis';

const { setNestedValue } = unlock( blockEditorPrivateApis );

// This is an incomplete, high-level approximation of the State type.
// It makes the selectors slightly more safe, but is intended to evolve
Expand Down
1 change: 0 additions & 1 deletion packages/core-data/src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ 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: 0 additions & 37 deletions packages/core-data/src/utils/set-nested-value.js

This file was deleted.

72 changes: 0 additions & 72 deletions packages/core-data/src/utils/test/set-nested-value.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
*/
import { DocumentOutline } from '../';

jest.mock( '@wordpress/block-editor', () => ( {
jest.mock( '', () => ( {
BlockTitle: () => 'Block Title',
...jest.requireActual( '@wordpress/block-editor' ),
} ) );

describe( 'DocumentOutline', () => {
Expand Down