diff --git a/packages/element/README.md b/packages/element/README.md index 8a3e39dc50a1ff..e67e760d7e4801 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -246,6 +246,23 @@ _Returns_ - `boolean`: True when an element is considered empty. +### isPlainObject + +Checks if the provided value is a plain object. + +Plain objects are objects that are either: + +- created by the `Object` constructor, or +- with a `[[Prototype]]` of `null`. + +_Parameters_ + +- _value_ `*`: Value to check. + +_Returns_ + +- `boolean`: True when value is considered a plain object. + ### isValidElement Checks if an object is a valid WPElement. diff --git a/packages/element/src/serialize.js b/packages/element/src/serialize.js index 9e14a089f8eb4c..770a0e1f3d10ec 100644 --- a/packages/element/src/serialize.js +++ b/packages/element/src/serialize.js @@ -28,7 +28,7 @@ /** * External dependencies */ -import { kebabCase, isPlainObject } from 'lodash'; +import { kebabCase } from 'lodash'; /** * WordPress dependencies @@ -44,6 +44,7 @@ import { */ import { createContext, Fragment, StrictMode, forwardRef } from './react'; import RawHTML from './raw-html'; +import { isPlainObject } from './utils'; /** @typedef {import('./react').WPElement} WPElement */ diff --git a/packages/element/src/test/utils.js b/packages/element/src/test/utils.js index e9f5f744e01fe6..556d8fed5a3244 100644 --- a/packages/element/src/test/utils.js +++ b/packages/element/src/test/utils.js @@ -2,7 +2,7 @@ * Internal dependencies */ import { createElement } from '../react'; -import { isEmptyElement } from '../utils'; +import { isEmptyElement, isPlainObject } from '../utils'; describe( 'isEmptyElement', () => { test( 'should be empty', () => { @@ -21,3 +21,40 @@ describe( 'isEmptyElement', () => { expect( isEmptyElement( [ 'x' ] ) ).toBe( false ); } ); } ); + +describe( 'isPlainObject', () => { + test( 'should return true for plain objects', () => { + expect( isPlainObject( { foo: 'bar' } ) ).toBe( true ); + expect( isPlainObject( new Object() ) ).toBe( true ); + expect( isPlainObject( Object.prototype ) ).toBe( true ); + expect( isPlainObject( Object.create( Object.prototype ) ) ).toBe( + true + ); + expect( isPlainObject( Object.create( null ) ) ).toBe( true ); + } ); + + test( 'should return false for anything else', () => { + expect( isPlainObject( undefined ) ).toBe( false ); + expect( isPlainObject( null ) ).toBe( false ); + expect( isPlainObject( true ) ).toBe( false ); + expect( isPlainObject( [ 1, 2, 3 ] ) ).toBe( false ); + expect( isPlainObject( '' ) ).toBe( false ); + expect( isPlainObject( 5 ) ).toBe( false ); + expect( isPlainObject( NaN ) ).toBe( false ); + expect( isPlainObject( Infinity ) ).toBe( false ); + expect( isPlainObject( new Array() ) ).toBe( false ); + expect( isPlainObject( new String( '' ) ) ).toBe( false ); + expect( isPlainObject( new Number( 5 ) ) ).toBe( false ); + expect( isPlainObject( /someRegex/ ) ).toBe( false ); + expect( isPlainObject( new Set( [ 1, 2, 3 ] ) ) ).toBe( false ); + expect( isPlainObject( function () {} ) ).toBe( false ); + expect( isPlainObject( () => {} ) ).toBe( false ); + expect( + isPlainObject( + new ( function () { + return this; + } )() + ) + ).toBe( false ); + } ); +} ); diff --git a/packages/element/src/utils.js b/packages/element/src/utils.js index 580094b78c4916..c6854aceb37f8a 100644 --- a/packages/element/src/utils.js +++ b/packages/element/src/utils.js @@ -15,3 +15,28 @@ export const isEmptyElement = ( element ) => { return ! element; }; + +/** + * Checks if the provided value is a plain object. + * + * Plain objects are objects that are either: + * - created by the `Object` constructor, or + * - with a `[[Prototype]]` of `null`. + * + * @param {*} value Value to check. + * @return {boolean} True when value is considered a plain object. + */ +export const isPlainObject = ( value ) => { + if ( typeof value !== 'object' || value === null ) { + return false; + } + + if ( Object.getPrototypeOf( value ) === null ) { + return true; + } + + return ( + value.constructor === Object && + Object.prototype.toString.call( value ) === '[object Object]' + ); +};