Skip to content

Commit

Permalink
feat(utils): replace ext lib assign-deep by local deepMerge util
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Nov 12, 2021
1 parent ec8e46a commit 2f56bd3
Show file tree
Hide file tree
Showing 13 changed files with 146 additions and 95 deletions.
1 change: 0 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
"not dead"
],
"dependencies": {
"assign-deep": "^1.0.1",
"dequal": "^2.0.2",
"dompurify": "^2.3.3",
"flatpickr": "^4.6.9",
Expand Down
6 changes: 2 additions & 4 deletions packages/common/src/extensions/slickCellRangeDecorator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as assign_ from 'assign-deep';
const assign = (assign_ as any)['default'] || assign_;

import { CellRange, CellRangeDecoratorOption, CSSStyleDeclarationWritable, SlickGrid } from '../interfaces/index';
import { createDomElement } from '../services/domUtilities';
import { deepMerge } from '../services/utilities';

/**
* Displays an overlay on top of a given cell range.
Expand All @@ -26,7 +24,7 @@ export class SlickCellRangeDecorator {
pluginName = 'CellRangeDecorator';

constructor(grid: SlickGrid, options?: Partial<CellRangeDecoratorOption>) {
this._addonOptions = assign({}, this._defaults, options);
this._addonOptions = deepMerge(this._defaults, options);
this._grid = grid;
}

Expand Down
6 changes: 2 additions & 4 deletions packages/common/src/extensions/slickCellRangeSelector.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import * as assign_ from 'assign-deep';
const assign = (assign_ as any)['default'] || assign_;

import { emptyElement, getHtmlElementOffset, } from '../services/domUtilities';
import { CellRange, CellRangeSelectorOption, DOMMouseEvent, DragPosition, DragRange, GridOption, OnScrollEventArgs, SlickEventHandler, SlickGrid, SlickNamespace } from '../interfaces/index';
import { SlickCellRangeDecorator } from './index';
import { deepMerge } from '../services/utilities';

// using external SlickGrid JS libraries
declare const Slick: SlickNamespace;
Expand Down Expand Up @@ -40,7 +38,7 @@ export class SlickCellRangeSelector {

constructor(options?: Partial<CellRangeSelectorOption>) {
this._eventHandler = new Slick.EventHandler();
this._addonOptions = assign({}, this._defaults, options);
this._addonOptions = deepMerge(this._defaults, options);
}

get addonOptions() {
Expand Down
5 changes: 2 additions & 3 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as BackendUtilities from './services/backendUtility.service';
import * as Observers from './services/observers';
import * as ServiceUtilities from './services/utilities';
import * as SortUtilities from './sortComparers/sortUtilities';
import * as assign_ from 'assign-deep';
const deepAssign = (assign_ as any)['default'] || assign_;
import { deepMerge } from './services/utilities';

// Public classes.
export * from './constants';
Expand All @@ -31,6 +30,6 @@ export * from './sortComparers/sortComparers.index';
export * from './services/index';
export { Enums } from './enums/enums.index';

const Utilities = { ...BackendUtilities, ...Observers, ...ServiceUtilities, ...SortUtilities, deepAssign };
const Utilities = { ...BackendUtilities, ...Observers, ...ServiceUtilities, ...SortUtilities, deepAssign: deepMerge };
export { Utilities };
export { SlickgridConfig } from './slickgrid-config';
117 changes: 90 additions & 27 deletions packages/common/src/services/__tests__/utilities.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
unflattenParentChildArrayToTree,
decimalFormatted,
deepCopy,
deepMerge,
emptyObject,
findItemInHierarchicalStructure,
findItemInTreeStructure,
Expand All @@ -31,7 +32,6 @@ import {
mapOperatorByFieldType,
mapOperatorToShorthandDesignation,
mapOperatorType,
mergeDeep,
parseBoolean,
parseUtcDate,
removeAccentFromText,
Expand Down Expand Up @@ -459,6 +459,95 @@ describe('Service/Utilies', () => {
});
});

describe('deepMerge method', () => {
it('should return undefined when both inputs are undefined', () => {
const obj1 = undefined;
const obj2 = null;
const output = deepMerge(obj1, obj2);
expect(output).toEqual(undefined);
});

it('should merge object even when 1st input is undefined because 2nd input is an object', () => {
const input1 = undefined;
const input2 = { firstName: 'John' };
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: 'John' });
});

it('should merge object even when 1st input is undefined because 2nd input is an object', () => {
const input1 = { firstName: 'John' };
const input2 = undefined;
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: 'John' });
});

it('should provide empty object as input and expect output object to include 2nd object', () => {
const input1 = {};
const input2 = { firstName: 'John' };
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: 'John' });
});

it('should provide filled object and return same object when 2nd object is also an object', () => {
const input1 = { firstName: 'Jane' };
const input2 = { firstName: { name: 'John' } };
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: { name: 'John' } });
});

it('should provide input object with undefined property and expect output object to return merged object from 2nd object when that one is filled', () => {
const input1 = { firstName: undefined };
const input2 = { firstName: {} };
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: {} });
});

it('should provide input object with undefined property and expect output object to return merged object from 2nd object when that one is filled', () => {
const input1 = { firstName: { name: 'John' } };
const input2 = { firstName: undefined };
const output = deepMerge(input1, input2);
expect(output).toEqual({ firstName: undefined });
});

it('should merge 2 objects and expect objects to be merged with both side', () => {
const input1 = { a: 1, b: 1, c: { x: 1, y: 1 }, d: [1, 1] };
const input2 = { b: 2, c: { y: 2, z: 2 }, d: [2, 2], e: 2 };

const output = deepMerge(input1, input2);
expect(output).toEqual({
a: 1, b: 2, c: { x: 1, y: 2, z: 2 },
d: [1, 1, 2, 2],
e: 2
});
});

it('should merge 3 objects and expect objects to be merged with both side', () => {
const input1 = { a: 1, b: 1, c: { x: 1, y: 1 }, d: [1, 1] };
const input2 = { b: 2, c: { y: 2, z: 2 } };
const input3 = { d: [2, 2], e: 2 };

const output = deepMerge(input1, input2, input3);
expect(output).toEqual({
a: 1, b: 2, c: { x: 1, y: 2, z: 2 },
d: [1, 1, 2, 2],
e: 2
});
});

it('should merge 3 objects, by calling deepMerge 2 times, and expect objects to be merged with both side', () => {
const input1 = { a: 1, b: 1, c: { x: 1, y: 1 }, d: [1, 1] };
const input2 = { b: 2, c: { y: 2, z: 2 } };
const input3 = { d: [2, 2], e: 2 };

const output = deepMerge(deepMerge(input1, input2), input3);
expect(output).toEqual({
a: 1, b: 2, c: { x: 1, y: 2, z: 2 },
d: [1, 1, 2, 2],
e: 2
});
});
});

describe('emptyObject method', () => {
it('should empty all object properties', () => {
const obj = { firstName: 'John', address: { zip: 123456, streetNumber: '123 Belleville Blvd' } };
Expand Down Expand Up @@ -1202,32 +1291,6 @@ describe('Service/Utilies', () => {
});
});

describe('mergeDeep method', () => {
it('should have undefined object when input object is also undefined', () => {
const inputObj = undefined;
mergeDeep(inputObj, { firstName: 'John' });
expect(inputObj).toEqual(undefined);
});

it('should provide empty object as input and expect output object to include 2nd object', () => {
const inputObj = {};
mergeDeep(inputObj, { firstName: 'John' });
expect(inputObj).toEqual({ firstName: 'John' });
});

it('should provide filled object and return same object when 2nd object is also an object', () => {
const inputObj = { firstName: 'Jane' };
mergeDeep(inputObj, { firstName: { name: 'John' } });
expect(inputObj).toEqual({ firstName: 'Jane' });
});

it('should provide input object with undefined property and expect output object to return merged object from 2nd object when that one is filled', () => {
const inputObj = { firstName: undefined };
mergeDeep(inputObj, { firstName: {} });
expect(inputObj).toEqual({ firstName: {} });
});
});

describe('parseBoolean method', () => {
it('should return false when input value is not parseable to a boolean', () => {
const output = parseBoolean('abc');
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/services/domUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const DOMPurify = DOMPurify_; // patch to fix rollup to work
import { InferType, SearchTerm } from '../enums/index';
import { Column, GridOption, HtmlElementPosition, SelectOption, SlickGrid, } from '../interfaces/index';
import { TranslaterService } from './translater.service';
import { mergeDeep } from './utilities';
import { deepMerge } from './utilities';

/**
* Create the HTML DOM Element for a Select Editor or Filter, this is specific to these 2 types only and the unit tests are directly under them
Expand Down Expand Up @@ -164,7 +164,7 @@ export function createDomElement<T extends keyof HTMLElementTagNameMap, K extend
Object.keys(elementOptions).forEach((elmOptionKey) => {
const elmValue = (elementOptions as any)[elmOptionKey];
if (typeof elmValue === 'object') {
mergeDeep(elm[elmOptionKey as K], elmValue);
deepMerge(elm[elmOptionKey as K], elmValue);
} else {
elm[elmOptionKey as K] = (elementOptions as any)[elmOptionKey];
}
Expand Down
72 changes: 45 additions & 27 deletions packages/common/src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,51 @@ export function deepCopy(objectOrArray: any | any[]): any | any[] {
return objectOrArray;
}

/**
* Performs a deep merge of objects and returns new object, it does not modify the source object, objects (immutable) and merges arrays via concatenation.
* Also, if first argument is undefined/null but next argument is an object then it will proceed and output will be an object
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
export function deepMerge(target: any, ...sources: any[]): any {
if (!sources.length) {
return target;
}
const source = sources.shift();

// when target is not an object but source is an object, then we'll assign as object
target = (!isObject(target) && isObject(source)) ? {} : target;

if (isObject(target) && isObject(source)) {
for (const prop in source) {
if (source.hasOwnProperty(prop)) {
if (prop in target) {
// handling merging of two properties with equal names
if (typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (target[prop].concat && source[prop].concat) {
// two arrays get concatenated
target[prop] = target[prop].concat(source[prop]);
} else {
// two objects get merged recursively
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
// new properties get added to target
target[prop] = source[prop];
}
}
}
}
return deepMerge(target, ...sources);
}

/**
* Empty an object properties by looping through them all and deleting them
* @param obj - input object
Expand Down Expand Up @@ -835,33 +880,6 @@ export function mapOperatorByFieldType(fieldType: typeof FieldType[keyof typeof
return map;
}

/**
* Deep merge two objects.
* @param {*} target
* @param {*} ...sources
*/
export function mergeDeep(target: any, ...sources: any[]): any {
if (!sources.length) {
return target;
}
const source = sources.shift();

if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}

return mergeDeep(target, ...sources);
}

/**
* Takes an object and allow to provide a property key to omit from the original object
* @param {Object} obj - input object
Expand Down
3 changes: 0 additions & 3 deletions packages/common/typings/assign-deep/index.d.ts

This file was deleted.

3 changes: 0 additions & 3 deletions packages/composite-editor-component/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,5 @@ Vanilla Bundle implementation of a Composite Editor Modal Window which can do th
### Internal Dependencies
- [@slickgrid-universal/common](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/common)

### External Dependencies
- [assign-deep](https://github.com/jonschlinkert/assign-deep) to deeply assign Object properties/values

### Installation
Follow the instruction provided in the main [README](https://github.com/ghiscoding/slickgrid-universal#installation).
3 changes: 1 addition & 2 deletions packages/composite-editor-component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
"not dead"
],
"dependencies": {
"@slickgrid-universal/common": "^0.19.0",
"assign-deep": "^1.0.1"
"@slickgrid-universal/common": "^0.19.0"
},
"devDependencies": {
"cross-env": "^7.0.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import * as assign_ from 'assign-deep';
const assign = (assign_ as any)['default'] || assign_;

import {
BindingEventService,
Column,
Expand All @@ -13,6 +10,7 @@ import {
createDomElement,
CurrentRowSelection,
deepCopy,
deepMerge,
DOMEvent,
Editor,
EditorValidationResult,
Expand Down Expand Up @@ -218,7 +216,7 @@ export class SlickCompositeEditorComponent implements ExternalResource {
this._formValues = { ...this._formValues, [columnId]: newValue };
}

this._formValues = assign({}, this._itemDataContext, this._formValues);
this._formValues = deepMerge({}, this._itemDataContext, this._formValues);
}

/**
Expand Down

This file was deleted.

12 changes: 0 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2629,23 +2629,11 @@ [email protected], assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=

assign-deep@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/assign-deep/-/assign-deep-1.0.1.tgz#b6d21d74e2f28bf6592e4c0c541bed6ab59c5f27"
integrity sha512-CSXAX79mibneEYfqLT5FEmkqR5WXF+xDRjgQQuVf6wSCXCYU8/vHttPidNar7wJ5BFmKAo8Wei0rCtzb+M/yeA==
dependencies:
assign-symbols "^2.0.2"

assign-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=

assign-symbols@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-2.0.2.tgz#0fb9191dd9d617042746ecfc354f3a3d768a0c98"
integrity sha512-9sBQUQZMKFKcO/C3Bo6Rx4CQany0R0UeVcefNGRRdW2vbmaMOhV1sbmlXcQLcD56juLXbSGTBm0GGuvmrAF8pA==

astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
Expand Down

0 comments on commit 2f56bd3

Please sign in to comment.