From 6675b0d6db88f7b1b3cd4d0b34e8610a7551630f Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Tue, 1 Sep 2020 13:28:37 +0200 Subject: [PATCH 1/2] improve structure --- .../src/environment/environment.ts | 2 +- .../src/support/utils/extend.ts | 64 -------- .../src/support/utils/index.ts | 1 - .../src/support/utils/object.ts | 67 +++++++- .../tests/support/dom/dimensions.test.ts | 47 ++++++ .../tests/support/dom/offset.test.ts | 8 +- .../tests/support/utils/arrays.test.ts | 22 ++- .../tests/support/utils/extend.test.ts | 144 ----------------- .../tests/support/utils/object.test.ts | 145 +++++++++++++++++- 9 files changed, 282 insertions(+), 218 deletions(-) delete mode 100644 packages/overlayscrollbars/src/support/utils/extend.ts create mode 100644 packages/overlayscrollbars/tests/support/dom/dimensions.test.ts delete mode 100644 packages/overlayscrollbars/tests/support/utils/extend.test.ts diff --git a/packages/overlayscrollbars/src/environment/environment.ts b/packages/overlayscrollbars/src/environment/environment.ts index 80c283d..e956f7c 100644 --- a/packages/overlayscrollbars/src/environment/environment.ts +++ b/packages/overlayscrollbars/src/environment/environment.ts @@ -143,7 +143,7 @@ export class Environment { removeAttr(envElm, 'style'); removeElements(envElm); - if (nativeScrollbarIsOverlaid.x && nativeScrollbarIsOverlaid.y) { + if (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y) { let size = windowSize(); let dpr = windowDPR(); const onChangedListener = this.#onChangedListener; diff --git a/packages/overlayscrollbars/src/support/utils/extend.ts b/packages/overlayscrollbars/src/support/utils/extend.ts deleted file mode 100644 index 406841d..0000000 --- a/packages/overlayscrollbars/src/support/utils/extend.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { isArray, isFunction, isPlainObject, isNull } from 'support/utils/types'; -import { each } from 'support/utils/array'; -import { keys } from 'support/utils/object'; - -// https://github.com/jquery/jquery/blob/master/src/core.js#L116 -export function extend(target: T, object1: U): T & U; -export function extend(target: T, object1: U, object2: V): T & U & V; -export function extend(target: T, object1: U, object2: V, object3: W): T & U & V & W; -export function extend(target: T, object1: U, object2: V, object3: W, object4: X): T & U & V & W & X; -export function extend(target: T, object1: U, object2: V, object3: W, object4: X, object5: Y): T & U & V & W & X & Y; -export function extend( - target: T, - object1?: U, - object2?: V, - object3?: W, - object4?: X, - object5?: Y, - object6?: Z -): T & U & V & W & X & Y & Z { - const sources: Array = [object1, object2, object3, object4, object5, object6]; - - // Handle case when target is a string or something (possible in deep copy) - if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) { - target = {} as T; - } - - each(sources, (source) => { - // Extend the base object - each(keys(source), (key) => { - const copy: any = source[key]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if (target === copy) { - return true; - } - - const copyIsArray = isArray(copy); - - // Recurse if we're merging plain objects or arrays - if (copy && (isPlainObject(copy) || copyIsArray)) { - const src = target[key]; - let clone: any = src; - - // Ensure proper type for the source value - if (copyIsArray && !isArray(src)) { - clone = []; - } else if (!copyIsArray && !isPlainObject(src)) { - clone = {}; - } - - // Never move original objects, clone them - target[key] = extend(clone, copy) as any; - - // Don't bring in undefined values - } else if (copy !== undefined) { - target[key] = copy; - } - }); - }); - - // Return the modified object - return target as any; -} diff --git a/packages/overlayscrollbars/src/support/utils/index.ts b/packages/overlayscrollbars/src/support/utils/index.ts index e275e87..3a3a49c 100644 --- a/packages/overlayscrollbars/src/support/utils/index.ts +++ b/packages/overlayscrollbars/src/support/utils/index.ts @@ -1,4 +1,3 @@ export * from 'support/utils/array'; export * from 'support/utils/object'; -export * from 'support/utils/extend'; export * from 'support/utils/types'; diff --git a/packages/overlayscrollbars/src/support/utils/object.ts b/packages/overlayscrollbars/src/support/utils/object.ts index c52ea1a..292f59d 100644 --- a/packages/overlayscrollbars/src/support/utils/object.ts +++ b/packages/overlayscrollbars/src/support/utils/object.ts @@ -1,13 +1,76 @@ +import { isArray, isFunction, isPlainObject, isNull } from 'support/utils/types'; +import { each } from 'support/utils/array'; + /** * Determines whether the passed object has a property with the passed name. * @param obj The object. * @param prop The name of the property. */ -export const hasOwnProperty = (obj: any, prop: string | number | symbol): boolean => - Object.prototype.hasOwnProperty.call(obj, prop); +export const hasOwnProperty = (obj: any, prop: string | number | symbol): boolean => Object.prototype.hasOwnProperty.call(obj, prop); /** * Returns the names of the enumerable string properties and methods of an object. * @param obj The object of which the properties shall be returned. */ export const keys = (obj: any): Array => (obj ? Object.keys(obj) : []); + +// https://github.com/jquery/jquery/blob/master/src/core.js#L116 +export function extend(target: T, object1: U): T & U; +export function extend(target: T, object1: U, object2: V): T & U & V; +export function extend(target: T, object1: U, object2: V, object3: W): T & U & V & W; +export function extend(target: T, object1: U, object2: V, object3: W, object4: X): T & U & V & W & X; +export function extend(target: T, object1: U, object2: V, object3: W, object4: X, object5: Y): T & U & V & W & X & Y; +export function extend( + target: T, + object1?: U, + object2?: V, + object3?: W, + object4?: X, + object5?: Y, + object6?: Z +): T & U & V & W & X & Y & Z { + const sources: Array = [object1, object2, object3, object4, object5, object6]; + + // Handle case when target is a string or something (possible in deep copy) + if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) { + target = {} as T; + } + + each(sources, (source) => { + // Extend the base object + each(keys(source), (key) => { + const copy: any = source[key]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if (target === copy) { + return true; + } + + const copyIsArray = isArray(copy); + + // Recurse if we're merging plain objects or arrays + if (copy && (isPlainObject(copy) || copyIsArray)) { + const src = target[key]; + let clone: any = src; + + // Ensure proper type for the source value + if (copyIsArray && !isArray(src)) { + clone = []; + } else if (!copyIsArray && !isPlainObject(src)) { + clone = {}; + } + + // Never move original objects, clone them + target[key] = extend(clone, copy) as any; + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[key] = copy; + } + }); + }); + + // Return the modified object + return target as any; +} diff --git a/packages/overlayscrollbars/tests/support/dom/dimensions.test.ts b/packages/overlayscrollbars/tests/support/dom/dimensions.test.ts new file mode 100644 index 0000000..b0078fc --- /dev/null +++ b/packages/overlayscrollbars/tests/support/dom/dimensions.test.ts @@ -0,0 +1,47 @@ +import { isNumber, isPlainObject } from 'support/utils/types'; +import { windowSize, offsetSize, clientSize, getBoundingClientRect } from 'support/dom/dimensions'; + +describe('dom dimensions', () => { + describe('offsetSize', () => { + test('DOM element', () => { + const result = offsetSize(document.body); + expect(isPlainObject(result)).toBe(true); + expect(isNumber(result.w)).toBe(true); + expect(isNumber(result.h)).toBe(true); + }); + + test('null', () => { + const result = offsetSize(null); + expect(isPlainObject(result)).toBe(true); + expect(isNumber(result.w)).toBe(true); + expect(isNumber(result.h)).toBe(true); + }); + }); + + describe('clientSize', () => { + test('DOM element', () => { + const result = clientSize(document.body); + expect(isPlainObject(result)).toBe(true); + expect(isNumber(result.w)).toBe(true); + expect(isNumber(result.h)).toBe(true); + }); + + test('null', () => { + const result = clientSize(null); + expect(isPlainObject(result)).toBe(true); + expect(isNumber(result.w)).toBe(true); + expect(isNumber(result.h)).toBe(true); + }); + }); + + test('windowSize', () => { + const result = windowSize(); + expect(isPlainObject(result)).toBe(true); + expect(isNumber(result.w)).toBe(true); + expect(isNumber(result.h)).toBe(true); + }); + + test('getBoundingClientRect', () => { + expect(getBoundingClientRect(document.body)).toEqual(document.body.getBoundingClientRect()); + }); +}); diff --git a/packages/overlayscrollbars/tests/support/dom/offset.test.ts b/packages/overlayscrollbars/tests/support/dom/offset.test.ts index dec3cc6..78245a4 100644 --- a/packages/overlayscrollbars/tests/support/dom/offset.test.ts +++ b/packages/overlayscrollbars/tests/support/dom/offset.test.ts @@ -3,14 +3,14 @@ import { offset, position } from 'support/dom/offset'; describe('dom offset', () => { describe('offset', () => { - test('returns correct object with DOM element', () => { + test('DOM element', () => { const result = offset(document.body); expect(isPlainObject(result)).toBe(true); expect(isNumber(result.x)).toBe(true); expect(isNumber(result.y)).toBe(true); }); - test('returns correct object with null', () => { + test('null', () => { const result = offset(null); expect(isPlainObject(result)).toBe(true); expect(result.x).toBe(0); @@ -19,14 +19,14 @@ describe('dom offset', () => { }); describe('position', () => { - test('returns correct object with DOM element', () => { + test('DOM element', () => { const result = position(document.body); expect(isPlainObject(result)).toBe(true); expect(isNumber(result.x)).toBe(true); expect(isNumber(result.y)).toBe(true); }); - test('returns correct object with null', () => { + test('null', () => { const result = position(null); expect(isPlainObject(result)).toBe(true); expect(result.x).toBe(0); diff --git a/packages/overlayscrollbars/tests/support/utils/arrays.test.ts b/packages/overlayscrollbars/tests/support/utils/arrays.test.ts index 30c1a25..d3b9bec 100644 --- a/packages/overlayscrollbars/tests/support/utils/arrays.test.ts +++ b/packages/overlayscrollbars/tests/support/utils/arrays.test.ts @@ -1,4 +1,4 @@ -import { each, indexOf } from 'support/utils/array'; +import { each, from, indexOf } from 'support/utils/array'; describe('array utilities', () => { describe('each', () => { @@ -172,6 +172,26 @@ describe('array utilities', () => { }); }); + describe('from', () => { + test('Array.from', () => { + document.body.innerHTML = '
'; + const fromChildNodes = from(document.body.childNodes); + expect(fromChildNodes).toEqual(Array.from(document.body.childNodes)); + document.body.innerHTML = ''; + }); + + test('fallback', () => { + document.body.innerHTML = '
'; + const arrFrom = Array.from; + // @ts-ignore + Array.from = undefined; + const fromChildNodes = from(document.body.childNodes); + Array.from = arrFrom; + expect(fromChildNodes).toEqual(Array.from(document.body.childNodes)); + document.body.innerHTML = ''; + }); + }); + test('indexOf', () => { const idx = indexOf([1, 2, 3], 2); expect(idx).toBe(1); diff --git a/packages/overlayscrollbars/tests/support/utils/extend.test.ts b/packages/overlayscrollbars/tests/support/utils/extend.test.ts deleted file mode 100644 index 4ee6125..0000000 --- a/packages/overlayscrollbars/tests/support/utils/extend.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { extend } from 'support/utils/extend'; -import { isPlainObject } from 'support/utils/types'; - -// type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T -type Deep = { - foo?: { - bar?: boolean; - baz?: boolean; - }; - foo2?: Document; -}; -type Settings = { - xnumber0?: null; - xnumber1?: number | null; - xnumber2?: number | null; - xstring1?: string; - xstring2?: string; - xxx?: string; -}; -type NestedArray = { - arr: Array | object; -}; - -// https://github.com/jquery/jquery/blob/master/test/unit/core.js#L965 -describe('extend', () => { - test('equals object assign', () => { - let settings: Settings = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; - const options: Settings = { xnumber2: 1, xstring2: 'x', xxx: 'newstring' }; - const optionsCopy: Settings = { xnumber2: 1, xstring2: 'x', xxx: 'newstring' }; - const merged: Settings = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'x', xxx: 'newstring' }; - - extend(settings, options); - expect(settings).toEqual(merged); - expect(options).toEqual(optionsCopy); - - extend(settings, null, options); - expect(settings).toEqual(merged); - expect(options).toEqual(optionsCopy); - - const deep1: Deep = { foo: { bar: true } }; - const deep2: Deep = { foo: { baz: true }, foo2: document }; - const deep2copy: Deep = { foo: { baz: true }, foo2: document }; - const deepmerged: Deep = { foo: { bar: true, baz: true }, foo2: document }; - - extend(deep1, deep2); - expect(deep1.foo).toEqual(deepmerged.foo); - expect(deep2.foo).toEqual(deep2copy.foo); - expect(deep1.foo2).toBe(document); - - const arr = [1, 2, 3]; - const nestedArray: NestedArray = { arr }; - - expect(extend({}, nestedArray).arr).not.toBe(arr); - expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); - expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); - expect(isPlainObject(extend({ arr }, { arr: {} }).arr)).toBeTruthy(); - - let empty: { foo?: any } = {}; - const optionsWithLength = { foo: { length: -1 } }; - - extend(empty, optionsWithLength); - expect(empty.foo).toEqual(optionsWithLength.foo); - - empty = {}; - const optionsWithDate = { foo: { date: new Date() } }; - - extend(empty, optionsWithDate); - expect(empty.foo).toEqual(optionsWithDate.foo); - - /** @constructor */ - const MyKlass = function () {}; - // @ts-ignore - const customObject = new MyKlass(); - const optionsWithCustomObject = { foo: { date: customObject } }; - empty = {}; - - extend(empty, optionsWithCustomObject); - expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); - - // Makes the class a little more realistic - MyKlass.prototype = { someMethod() {} }; - empty = {}; - - extend(empty, optionsWithCustomObject); - expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); - - const MyNumber = Number; - - let ret: any = extend({ foo: 4 }, { foo: new MyNumber(5) }); - expect(parseInt(ret.foo?.toString() as string, 10) === 5).toBeTruthy(); - - let nullUndef = extend({}, options, { xnumber2: null }); - expect(nullUndef.xnumber2).toBe(null); - - // @ts-ignore - nullUndef = extend({}, options, { xnumber2: undefined }); - expect(nullUndef.xnumber2).toBe(options.xnumber2); - - // @ts-ignore - nullUndef = extend({}, options, { xnumber0: null }); - expect(nullUndef.xnumber0).toBe(null); - - const target = {}; - const recursive = { foo: target, bar: 5 }; - - extend(target, recursive); - expect(target).toEqual({ bar: 5 }); - - ret = extend({ foo: [] }, { foo: [0] }); - expect(ret.foo?.length).toBe(1); - - ret = extend({ foo: '1,2,3' }, { foo: [1, 2, 3] }); - expect(typeof ret.foo !== 'string').toBeTruthy(); - - ret = extend({ foo: 'bar' }, { foo: null }); - expect(typeof ret.foo !== 'undefined').toBeTruthy(); - - const obj = { foo: null }; - extend(obj, { foo: 'notnull' }); - expect(obj.foo).toBe('notnull'); - - const func: { (): void; key?: string } = () => {}; - extend(func, { key: 'value' }); - expect(func.key).toBe('value'); - - const defaults = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; - const defaultsCopy = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; - const options1 = { xnumber2: 1, xstring2: 'x' }; - const options1Copy = { xnumber2: 1, xstring2: 'x' }; - const options2 = { xstring2: 'xx', xxx: 'newstringx' }; - const options2Copy = { xstring2: 'xx', xxx: 'newstringx' }; - const merged2 = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'xx', xxx: 'newstringx' }; - - settings = extend({}, defaults, options1, options2); - expect(settings).toEqual(merged2); - expect(defaults).toEqual(defaultsCopy); - expect(options1).toEqual(options1Copy); - expect(options2).toEqual(options2Copy); - - expect(extend('', { foo: 1 })).toEqual({ foo: 1 }); - expect(extend(null, { foo: null, deep: { foo: null } })).toEqual({ foo: null, deep: { foo: null } }); - expect(extend(12, { foo: 1, deep: { foo: null, text: '' } })).toEqual({ foo: 1, deep: { foo: null, text: '' } }); - }); -}); diff --git a/packages/overlayscrollbars/tests/support/utils/object.test.ts b/packages/overlayscrollbars/tests/support/utils/object.test.ts index a84d71c..e771c11 100644 --- a/packages/overlayscrollbars/tests/support/utils/object.test.ts +++ b/packages/overlayscrollbars/tests/support/utils/object.test.ts @@ -1,6 +1,149 @@ -import { keys, hasOwnProperty } from 'support/utils/object'; +import { extend, keys, hasOwnProperty } from 'support/utils/object'; +import { isPlainObject } from 'support/utils/types'; describe('object utilities', () => { + // https://github.com/jquery/jquery/blob/master/test/unit/core.js#L965 + describe('extend', () => { + // type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T + type Deep = { + foo?: { + bar?: boolean; + baz?: boolean; + }; + foo2?: Document; + }; + type Settings = { + xnumber0?: null; + xnumber1?: number | null; + xnumber2?: number | null; + xstring1?: string; + xstring2?: string; + xxx?: string; + }; + type NestedArray = { + arr: Array | object; + }; + + test('equals object assign', () => { + let settings: Settings = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; + const options: Settings = { xnumber2: 1, xstring2: 'x', xxx: 'newstring' }; + const optionsCopy: Settings = { xnumber2: 1, xstring2: 'x', xxx: 'newstring' }; + const merged: Settings = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'x', xxx: 'newstring' }; + + extend(settings, options); + expect(settings).toEqual(merged); + expect(options).toEqual(optionsCopy); + + extend(settings, null, options); + expect(settings).toEqual(merged); + expect(options).toEqual(optionsCopy); + + const deep1: Deep = { foo: { bar: true } }; + const deep2: Deep = { foo: { baz: true }, foo2: document }; + const deep2copy: Deep = { foo: { baz: true }, foo2: document }; + const deepmerged: Deep = { foo: { bar: true, baz: true }, foo2: document }; + + extend(deep1, deep2); + expect(deep1.foo).toEqual(deepmerged.foo); + expect(deep2.foo).toEqual(deep2copy.foo); + expect(deep1.foo2).toBe(document); + + const arr = [1, 2, 3]; + const nestedArray: NestedArray = { arr }; + + expect(extend({}, nestedArray).arr).not.toBe(arr); + expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); + expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); + expect(isPlainObject(extend({ arr }, { arr: {} }).arr)).toBeTruthy(); + + let empty: { foo?: any } = {}; + const optionsWithLength = { foo: { length: -1 } }; + + extend(empty, optionsWithLength); + expect(empty.foo).toEqual(optionsWithLength.foo); + + empty = {}; + const optionsWithDate = { foo: { date: new Date() } }; + + extend(empty, optionsWithDate); + expect(empty.foo).toEqual(optionsWithDate.foo); + + /** @constructor */ + const MyKlass = function () {}; + // @ts-ignore + const customObject = new MyKlass(); + const optionsWithCustomObject = { foo: { date: customObject } }; + empty = {}; + + extend(empty, optionsWithCustomObject); + expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); + + // Makes the class a little more realistic + MyKlass.prototype = { someMethod() {} }; + empty = {}; + + extend(empty, optionsWithCustomObject); + expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); + + const MyNumber = Number; + + let ret: any = extend({ foo: 4 }, { foo: new MyNumber(5) }); + expect(parseInt(ret.foo?.toString() as string, 10) === 5).toBeTruthy(); + + let nullUndef = extend({}, options, { xnumber2: null }); + expect(nullUndef.xnumber2).toBe(null); + + // @ts-ignore + nullUndef = extend({}, options, { xnumber2: undefined }); + expect(nullUndef.xnumber2).toBe(options.xnumber2); + + // @ts-ignore + nullUndef = extend({}, options, { xnumber0: null }); + expect(nullUndef.xnumber0).toBe(null); + + const target = {}; + const recursive = { foo: target, bar: 5 }; + + extend(target, recursive); + expect(target).toEqual({ bar: 5 }); + + ret = extend({ foo: [] }, { foo: [0] }); + expect(ret.foo?.length).toBe(1); + + ret = extend({ foo: '1,2,3' }, { foo: [1, 2, 3] }); + expect(typeof ret.foo !== 'string').toBeTruthy(); + + ret = extend({ foo: 'bar' }, { foo: null }); + expect(typeof ret.foo !== 'undefined').toBeTruthy(); + + const obj = { foo: null }; + extend(obj, { foo: 'notnull' }); + expect(obj.foo).toBe('notnull'); + + const func: { (): void; key?: string } = () => {}; + extend(func, { key: 'value' }); + expect(func.key).toBe('value'); + + const defaults = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; + const defaultsCopy = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; + const options1 = { xnumber2: 1, xstring2: 'x' }; + const options1Copy = { xnumber2: 1, xstring2: 'x' }; + const options2 = { xstring2: 'xx', xxx: 'newstringx' }; + const options2Copy = { xstring2: 'xx', xxx: 'newstringx' }; + const merged2 = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'xx', xxx: 'newstringx' }; + + settings = extend({}, defaults, options1, options2); + expect(settings).toEqual(merged2); + expect(defaults).toEqual(defaultsCopy); + expect(options1).toEqual(options1Copy); + expect(options2).toEqual(options2Copy); + + expect(extend('', { foo: 1 })).toEqual({ foo: 1 }); + expect(extend(null, { foo: null, deep: { foo: null } })).toEqual({ foo: null, deep: { foo: null } }); + expect(extend(12, { foo: 1, deep: { foo: null, text: '' } })).toEqual({ foo: 1, deep: { foo: null, text: '' } }); + }); + }); + describe('keys', () => { test('correct amount of keys', () => { const obj = { From 71fabc3a3660b57af209f380149c50024131dcbc Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 2 Sep 2020 15:08:27 +0200 Subject: [PATCH 2/2] improve documentation and tests --- .../src/environment/environment.ts | 8 +-- .../src/environment/index.ts | 3 + .../src/support/compatibility/vendors.ts | 2 +- .../src/support/dom/create.ts | 7 +++ .../src/support/dom/dimensions.ts | 15 +++++ .../src/support/dom/manipulation.ts | 6 +- .../src/support/dom/offset.ts | 12 +++- .../src/support/dom/style.ts | 13 ++++ .../src/support/dom/traversal.ts | 28 +++++++++ .../src/support/utils/array.ts | 4 ++ .../src/support/utils/object.ts | 18 +++--- .../tests/support/dom/offset.test.ts | 14 ++--- .../tests/support/options/validation.test.ts | 60 +++++++++---------- .../tests/support/utils/object.test.ts | 58 +++++++++--------- 14 files changed, 164 insertions(+), 84 deletions(-) diff --git a/packages/overlayscrollbars/src/environment/environment.ts b/packages/overlayscrollbars/src/environment/environment.ts index e956f7c..7161567 100644 --- a/packages/overlayscrollbars/src/environment/environment.ts +++ b/packages/overlayscrollbars/src/environment/environment.ts @@ -4,7 +4,7 @@ import { style, appendChildren, clientSize, - offset, + absoluteCoordinates, offsetSize, scrollLeft, jsAPI, @@ -45,10 +45,10 @@ const rtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: style(parentElm, { overflowX: strHidden, overflowY: strHidden }); scrollLeft(parentElm, 0); - const parentOffset = offset(parentElm); - const childOffset = offset(childElm); + const parentOffset = absoluteCoordinates(parentElm); + const childOffset = absoluteCoordinates(childElm); scrollLeft(parentElm, -999); // https://github.com/KingSora/OverlayScrollbars/issues/187 - const childOffsetAfterScroll = offset(childElm); + const childOffsetAfterScroll = absoluteCoordinates(childElm); return { /** * origin direction = determines if the zero scroll position is on the left or right side diff --git a/packages/overlayscrollbars/src/environment/index.ts b/packages/overlayscrollbars/src/environment/index.ts index c0f4de3..247d7e7 100644 --- a/packages/overlayscrollbars/src/environment/index.ts +++ b/packages/overlayscrollbars/src/environment/index.ts @@ -1 +1,4 @@ +import { Environment } from 'environment/environment'; + export * from 'environment/environment'; +export type OSEnvironment = Omit; diff --git a/packages/overlayscrollbars/src/support/compatibility/vendors.ts b/packages/overlayscrollbars/src/support/compatibility/vendors.ts index c7f3d6c..6ca3b02 100644 --- a/packages/overlayscrollbars/src/support/compatibility/vendors.ts +++ b/packages/overlayscrollbars/src/support/compatibility/vendors.ts @@ -81,7 +81,7 @@ export const cssPropertyValue = (property: string, values: string, suffix?: stri * Get the requested JS function, object or constructor with vendor prefix if it isn't supported without or undefined if unsupported. * @param name The name of the JS function, object or constructor. */ -export const jsAPI = (name: string): any => { +export const jsAPI = (name: string): T | undefined => { let result: any = jsCache[name] || window[name]; if (hasOwnProperty(jsCache, name)) { diff --git a/packages/overlayscrollbars/src/support/dom/create.ts b/packages/overlayscrollbars/src/support/dom/create.ts index d39c5fc..3a704ca 100644 --- a/packages/overlayscrollbars/src/support/dom/create.ts +++ b/packages/overlayscrollbars/src/support/dom/create.ts @@ -2,8 +2,15 @@ import { each } from 'support/utils/array'; import { contents } from 'support/dom/traversal'; import { removeElements } from 'support/dom/manipulation'; +/** + * Creates a div DOM node. + */ export const createDiv = (): HTMLDivElement => document.createElement('div'); +/** + * Creates DOM nodes modeled after the passed html string and returns the root dom nodes as a array. + * @param html The html string after which the DOM nodes shall be created. + */ export const createDOM = (html: string): ReadonlyArray => { const createdDiv = createDiv(); createdDiv.innerHTML = html.trim(); diff --git a/packages/overlayscrollbars/src/support/dom/dimensions.ts b/packages/overlayscrollbars/src/support/dom/dimensions.ts index da8f56d..a26fb1a 100644 --- a/packages/overlayscrollbars/src/support/dom/dimensions.ts +++ b/packages/overlayscrollbars/src/support/dom/dimensions.ts @@ -5,11 +5,18 @@ const zeroObj: WH = { h: 0, }; +/** + * Returns the window inner- width and height. + */ export const windowSize = (): WH => ({ w: window.innerWidth, h: window.innerHeight, }); +/** + * Returns the offset- width and height of the passed element. If the element is null the width and height values are 0. + * @param elm The element of which the offset- width and height shall be returned. + */ export const offsetSize = (elm: HTMLElement | null): WH => elm ? { @@ -18,6 +25,10 @@ export const offsetSize = (elm: HTMLElement | null): WH => } : zeroObj; +/** + * Returns the client- width and height of the passed element. If the element is null the width and height values are 0. + * @param elm The element of which the client- width and height shall be returned. + */ export const clientSize = (elm: HTMLElement | null): WH => elm ? { @@ -26,4 +37,8 @@ export const clientSize = (elm: HTMLElement | null): WH => } : zeroObj; +/** + * Returns the BoundingClientRect of the passed element. + * @param elm The element of which the BoundingClientRect shall be returned. + */ export const getBoundingClientRect = (elm: HTMLElement): DOMRect => elm.getBoundingClientRect(); diff --git a/packages/overlayscrollbars/src/support/dom/manipulation.ts b/packages/overlayscrollbars/src/support/dom/manipulation.ts index adc96c2..fc8831f 100644 --- a/packages/overlayscrollbars/src/support/dom/manipulation.ts +++ b/packages/overlayscrollbars/src/support/dom/manipulation.ts @@ -89,9 +89,9 @@ export const removeElements = (nodes: NodeCollection): void => { if (isArrayLike(nodes)) { each(from(nodes), (e) => removeElements(e)); } else if (nodes) { - const { parentNode } = nodes; - if (parentNode) { - parentNode.removeChild(nodes); + const parentElm = parent(nodes); + if (parentElm) { + parentElm.removeChild(nodes); } } }; diff --git a/packages/overlayscrollbars/src/support/dom/offset.ts b/packages/overlayscrollbars/src/support/dom/offset.ts index 16e54d9..854c49d 100644 --- a/packages/overlayscrollbars/src/support/dom/offset.ts +++ b/packages/overlayscrollbars/src/support/dom/offset.ts @@ -6,7 +6,11 @@ const zeroObj: XY = { y: 0, }; -export const offset = (elm: HTMLElement | null): XY => { +/** + * Returns the offset- left and top coordinates of the passed element relative to the document. If the element is null the top and left values are 0. + * @param elm The element of which the offset- top and left coordinates shall be returned. + */ +export const absoluteCoordinates = (elm: HTMLElement | null): XY => { const rect = elm ? getBoundingClientRect(elm) : 0; return rect ? { @@ -16,7 +20,11 @@ export const offset = (elm: HTMLElement | null): XY => { : zeroObj; }; -export const position = (elm: HTMLElement | null): XY => +/** + * Returns the offset- left and top coordinates of the passed element. If the element is null the top and left values are 0. + * @param elm The element of which the offset- top and left coordinates shall be returned. + */ +export const offsetCoordinates = (elm: HTMLElement | null): XY => elm ? { x: elm.offsetLeft, diff --git a/packages/overlayscrollbars/src/support/dom/style.ts b/packages/overlayscrollbars/src/support/dom/style.ts index cc00b06..9913163 100644 --- a/packages/overlayscrollbars/src/support/dom/style.ts +++ b/packages/overlayscrollbars/src/support/dom/style.ts @@ -31,6 +31,11 @@ const setCSSVal = (elm: HTMLElement | null, prop: string, val: string | number): } catch (e) {} }; +/** + * Gets or sets the passed styles to the passed element. + * @param elm The element to which the styles shall be applied to / be read from. + * @param styles The styles which shall be set or read. + */ export function style(elm: HTMLElement | null, styles: CssStyles): void; export function style(elm: HTMLElement | null, styles: string): string; export function style(elm: HTMLElement | null, styles: Array | string): { [key: string]: string }; @@ -54,10 +59,18 @@ export function style(elm: HTMLElement | null, styles: CssStyles | Array each(keys(styles), (key) => setCSSVal(elm, key, styles[key])); } +/** + * Hides the passed element (display: none). + * @param elm The element which shall be hidden. + */ export const hide = (elm: HTMLElement | null): void => { style(elm, { display: 'none' }); }; +/** + * Shows the passed element (display: block). + * @param elm The element which shall be shown. + */ export const show = (elm: HTMLElement | null): void => { style(elm, { display: 'block' }); }; diff --git a/packages/overlayscrollbars/src/support/dom/traversal.ts b/packages/overlayscrollbars/src/support/dom/traversal.ts index 41a601d..df61979 100644 --- a/packages/overlayscrollbars/src/support/dom/traversal.ts +++ b/packages/overlayscrollbars/src/support/dom/traversal.ts @@ -2,6 +2,11 @@ import { each, from } from 'support/utils/array'; const elementIsVisible = (elm: HTMLElement): boolean => !!(elm.offsetWidth || elm.offsetHeight || elm.getClientRects().length); +/** + * Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided. + * @param selector The selector which has to be searched by. + * @param elm The element from which the search shall be outgoing. + */ export const find = (selector: string, elm?: Element | null): ReadonlyArray => { const arr: Array = []; @@ -12,8 +17,18 @@ export const find = (selector: string, elm?: Element | null): ReadonlyArray (elm || document).querySelector(selector); +/** + * Determines whether the passed element is matching with the passed selector. + * @param elm The element which has to be compared with the passed selector. + * @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'. + */ export const is = (elm: Element | null, selector: string): boolean => { if (elm) { if (selector === ':visible') { @@ -29,6 +44,11 @@ export const is = (elm: Element | null, selector: string): boolean => { return false; }; +/** + * Returns the children (no text-nodes or comments) of the passed element which are matching the passed selector. An empty array is returned if the passed element is null. + * @param elm The element of which the children shall be returned. + * @param selector The selector which must match with the children elements. + */ export const children = (elm: Element | null, selector?: string): ReadonlyArray => { const childs: Array = []; @@ -45,6 +65,14 @@ export const children = (elm: Element | null, selector?: string): ReadonlyArray< return childs; }; +/** + * Returns the childNodes (incl. text-nodes or comments etc.) of the passed element. An empty array is returned if the passed element is null. + * @param elm The element of which the childNodes shall be returned. + */ export const contents = (elm: Element | null): ReadonlyArray => (elm ? from(elm.childNodes) : []); +/** + * Returns the parent element of the passed element, or null if the passed element is null. + * @param elm The element of which the parent element shall be returned. + */ export const parent = (elm: Node | null): Node | null => (elm ? elm.parentElement : null); diff --git a/packages/overlayscrollbars/src/support/utils/array.ts b/packages/overlayscrollbars/src/support/utils/array.ts index 3b2248e..e07fe0e 100644 --- a/packages/overlayscrollbars/src/support/utils/array.ts +++ b/packages/overlayscrollbars/src/support/utils/array.ts @@ -51,6 +51,10 @@ export function each( */ export const indexOf = (arr: Array, item: T, fromIndex?: number): number => arr.indexOf(item, fromIndex); +/** + * Creates a shallow-copied Array instance from an array-like or iterable object. + * @param arr The object from which the array instance shall be created. + */ export const from = (arr: ArrayLike) => { if (Array.from) { return Array.from(arr); diff --git a/packages/overlayscrollbars/src/support/utils/object.ts b/packages/overlayscrollbars/src/support/utils/object.ts index 292f59d..46416bf 100644 --- a/packages/overlayscrollbars/src/support/utils/object.ts +++ b/packages/overlayscrollbars/src/support/utils/object.ts @@ -15,12 +15,12 @@ export const hasOwnProperty = (obj: any, prop: string | number | symbol): boolea export const keys = (obj: any): Array => (obj ? Object.keys(obj) : []); // https://github.com/jquery/jquery/blob/master/src/core.js#L116 -export function extend(target: T, object1: U): T & U; -export function extend(target: T, object1: U, object2: V): T & U & V; -export function extend(target: T, object1: U, object2: V, object3: W): T & U & V & W; -export function extend(target: T, object1: U, object2: V, object3: W, object4: X): T & U & V & W & X; -export function extend(target: T, object1: U, object2: V, object3: W, object4: X, object5: Y): T & U & V & W & X & Y; -export function extend( +export function assignDeep(target: T, object1: U): T & U; +export function assignDeep(target: T, object1: U, object2: V): T & U & V; +export function assignDeep(target: T, object1: U, object2: V, object3: W): T & U & V & W; +export function assignDeep(target: T, object1: U, object2: V, object3: W, object4: X): T & U & V & W & X; +export function assignDeep(target: T, object1: U, object2: V, object3: W, object4: X, object5: Y): T & U & V & W & X & Y; +export function assignDeep( target: T, object1?: U, object2?: V, @@ -62,10 +62,8 @@ export function extend( } // Never move original objects, clone them - target[key] = extend(clone, copy) as any; - - // Don't bring in undefined values - } else if (copy !== undefined) { + target[key] = assignDeep(clone, copy) as any; + } else { target[key] = copy; } }); diff --git a/packages/overlayscrollbars/tests/support/dom/offset.test.ts b/packages/overlayscrollbars/tests/support/dom/offset.test.ts index 78245a4..42c7f90 100644 --- a/packages/overlayscrollbars/tests/support/dom/offset.test.ts +++ b/packages/overlayscrollbars/tests/support/dom/offset.test.ts @@ -1,33 +1,33 @@ import { isNumber, isPlainObject } from 'support/utils/types'; -import { offset, position } from 'support/dom/offset'; +import { absoluteCoordinates, offsetCoordinates } from 'support/dom/offset'; describe('dom offset', () => { - describe('offset', () => { + describe('absoluteCoordinates', () => { test('DOM element', () => { - const result = offset(document.body); + const result = absoluteCoordinates(document.body); expect(isPlainObject(result)).toBe(true); expect(isNumber(result.x)).toBe(true); expect(isNumber(result.y)).toBe(true); }); test('null', () => { - const result = offset(null); + const result = absoluteCoordinates(null); expect(isPlainObject(result)).toBe(true); expect(result.x).toBe(0); expect(result.y).toBe(0); }); }); - describe('position', () => { + describe('offsetCoordinates', () => { test('DOM element', () => { - const result = position(document.body); + const result = offsetCoordinates(document.body); expect(isPlainObject(result)).toBe(true); expect(isNumber(result.x)).toBe(true); expect(isNumber(result.y)).toBe(true); }); test('null', () => { - const result = position(null); + const result = offsetCoordinates(null); expect(isPlainObject(result)).toBe(true); expect(result.x).toBe(0); expect(result.y).toBe(0); diff --git a/packages/overlayscrollbars/tests/support/options/validation.test.ts b/packages/overlayscrollbars/tests/support/options/validation.test.ts index 74e426d..b2087e6 100644 --- a/packages/overlayscrollbars/tests/support/options/validation.test.ts +++ b/packages/overlayscrollbars/tests/support/options/validation.test.ts @@ -1,5 +1,5 @@ import { validate, optionsTemplateTypes as oTypes, OptionsTemplate } from 'support/options'; -import { extend, isEmptyObject } from 'support/utils'; +import { assignDeep, isEmptyObject } from 'support/utils'; type TestOptionsObj = { propA: 'propA'; null: null }; type TestOptionsEnum = 'A' | 'B' | 'C'; @@ -55,7 +55,7 @@ describe('options validation', () => { foreignProp: 'foreign', foreignDeep: { a: 'A', b: 'B' }, }; - const modifiedOptions = extend({}, options, { nested: foreignObj }, foreignObj); + const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj); const result = validate(modifiedOptions, template); const { validated } = result; @@ -63,14 +63,14 @@ describe('options validation', () => { }); test('passed objects arent mutated', () => { - const clonedOptions = extend({}, options); + const clonedOptions = assignDeep({}, options); validate(clonedOptions, template, clonedOptions); expect(clonedOptions).toEqual(options); }); test('passed object isnt returned object', () => { - const clonedOptions = extend({}, options); + const clonedOptions = assignDeep({}, options); const result = validate(clonedOptions, template); expect(result.validated).not.toBe(clonedOptions); @@ -86,7 +86,7 @@ describe('options validation', () => { test('return signle non-object foreign property', () => { const foreignObj = { foreignProp: 'foreign' }; - const modifiedOptions = extend({}, options, foreignObj); + const modifiedOptions = assignDeep({}, options, foreignObj); const result = validate(modifiedOptions, template); const { foreign } = result; @@ -98,7 +98,7 @@ describe('options validation', () => { foreignProp: 'foreign', foreignDeep: { a: 'A', b: 'B' }, }; - const modifiedOptions = extend({}, options, foreignObj); + const modifiedOptions = assignDeep({}, options, foreignObj); const result = validate(modifiedOptions, template); const { foreign } = result; @@ -110,7 +110,7 @@ describe('options validation', () => { foreignProp: 'foreign', foreignDeep: { a: 'A', b: 'B' }, }; - const modifiedOptions = extend({}, options, { nested: foreignObj }, foreignObj); + const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj); const result = validate(modifiedOptions, template); const { foreign } = result; @@ -122,7 +122,7 @@ describe('options validation', () => { describe('diff property return', () => { test('one value changed', () => { - const modifiedOptions = extend({}, options, { str: 'newvaluetest' }); + const modifiedOptions = assignDeep({}, options, { str: 'newvaluetest' }); const result = validate(modifiedOptions, template, options); const { validated } = result; @@ -132,7 +132,7 @@ describe('options validation', () => { }); test('multiple values changed', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { str: 'newvaluetest', nullbool: null, }); @@ -147,7 +147,7 @@ describe('options validation', () => { }); test('one nested value changed', () => { - const modifiedOptions = extend({}, options, { nested: { num: -1293 } }); + const modifiedOptions = assignDeep({}, options, { nested: { num: -1293 } }); const result = validate(modifiedOptions, template, options); const { validated } = result; @@ -159,7 +159,7 @@ describe('options validation', () => { }); test('multiple nested values changed', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { num: -1293, abc: 'C' }, }); const result = validate(modifiedOptions, template, options); @@ -176,7 +176,7 @@ describe('options validation', () => { test('various values changed', () => { const newFunc = () => {}; - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { str: 'newstrvalue', func: newFunc, abc: 'C', @@ -206,7 +206,7 @@ describe('options validation', () => { foreignDeep: { a: 'A', b: 'B' }, }; const newFunc = () => {}; - const modifiedOptions = extend( + const modifiedOptions = assignDeep( {}, options, { @@ -239,7 +239,7 @@ describe('options validation', () => { describe('value validity', () => { test('single value doesnt match template', () => { - const modifiedOptions = extend({}, options, { str: 1 }); + const modifiedOptions = assignDeep({}, options, { str: 1 }); const result = validate(modifiedOptions, template); const { validated } = result; @@ -247,7 +247,7 @@ describe('options validation', () => { }); test('single enum value doesnt match template', () => { - const modifiedOptions = extend({}, options, { abc: 'testval' }); + const modifiedOptions = assignDeep({}, options, { abc: 'testval' }); const result = validate(modifiedOptions, template); const { validated } = result; @@ -255,7 +255,7 @@ describe('options validation', () => { }); test('multiple values dont match template', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { str: 1, abc: 'testval', nullbool: 'string', @@ -269,7 +269,7 @@ describe('options validation', () => { }); test('single nested value dont match template', () => { - const modifiedOptions = extend({}, options, { nested: { num: 'hi' } }); + const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi' } }); const result = validate(modifiedOptions, template); const { validated } = result; @@ -277,7 +277,7 @@ describe('options validation', () => { }); test('single nested enum value dont match template', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { abc: 'testabc' }, }); const result = validate(modifiedOptions, template); @@ -287,7 +287,7 @@ describe('options validation', () => { }); test('multiple nested values dont match template', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi', abc: 'testabc' }, }); const result = validate(modifiedOptions, template); @@ -298,7 +298,7 @@ describe('options validation', () => { }); test('all nested values dont match template', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi', abc: 'testabc', switch: 1 }, }); const result = validate(modifiedOptions, template); @@ -308,7 +308,7 @@ describe('options validation', () => { }); test('all nested values dont match template with foreign property', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { foreign: 'foreign', num: 'hi', @@ -323,7 +323,7 @@ describe('options validation', () => { }); test('various values dont match template', () => { - const modifiedOptions = extend({}, options, { + const modifiedOptions = assignDeep({}, options, { nested: { switch: null }, obj: 1, abc: 'testest', @@ -343,7 +343,7 @@ describe('options validation', () => { foreignProp: 'foreign', foreignDeep: { a: 'A', b: 'B' }, }; - const modifiedOptions = extend( + const modifiedOptions = assignDeep( {}, options, { @@ -369,7 +369,7 @@ describe('options validation', () => { }); test('nested object is string', () => { - const modifiedOptions = extend({}, options, { nested: 'string' }); + const modifiedOptions = assignDeep({}, options, { nested: 'string' }); const result = validate(modifiedOptions, template); const { validated } = result; @@ -377,7 +377,7 @@ describe('options validation', () => { }); test('nested object is null', () => { - const modifiedOptions = extend({}, options, { nested: null }); + const modifiedOptions = assignDeep({}, options, { nested: null }); const result = validate(modifiedOptions, template); const { validated } = result; @@ -385,7 +385,7 @@ describe('options validation', () => { }); test('nested object is undefined', () => { - const modifiedOptions = extend({}, options); + const modifiedOptions = assignDeep({}, options); modifiedOptions.nested = undefined; const result = validate(modifiedOptions, template); const { validated } = result; @@ -409,7 +409,7 @@ describe('options validation', () => { const { warn } = console; console.warn = jest.fn(); - const modifiedOptions = extend({}, options, { str: 1 }); + const modifiedOptions = assignDeep({}, options, { str: 1 }); validate(modifiedOptions, template, {}, false); expect(console.warn).not.toBeCalled(); @@ -421,15 +421,15 @@ describe('options validation', () => { console.warn = jest.fn(); // str must be string - validate(extend({}, options, { str: 1 }), template, {}, true); + validate(assignDeep({}, options, { str: 1 }), template, {}, true); expect(console.warn).toBeCalledTimes(1); // abc must be A | B | C - validate(extend({}, options, { abc: 'some string' }), template, {}, true); + validate(assignDeep({}, options, { abc: 'some string' }), template, {}, true); expect(console.warn).toBeCalledTimes(2); // everthing OK - validate(extend({}, options, { abc: 'C' }), template, {}, true); + validate(assignDeep({}, options, { abc: 'C' }), template, {}, true); expect(console.warn).toBeCalledTimes(2); console.warn = warn; diff --git a/packages/overlayscrollbars/tests/support/utils/object.test.ts b/packages/overlayscrollbars/tests/support/utils/object.test.ts index e771c11..ab8ca83 100644 --- a/packages/overlayscrollbars/tests/support/utils/object.test.ts +++ b/packages/overlayscrollbars/tests/support/utils/object.test.ts @@ -1,9 +1,9 @@ -import { extend, keys, hasOwnProperty } from 'support/utils/object'; +import { assignDeep, keys, hasOwnProperty } from 'support/utils/object'; import { isPlainObject } from 'support/utils/types'; describe('object utilities', () => { // https://github.com/jquery/jquery/blob/master/test/unit/core.js#L965 - describe('extend', () => { + describe('assignDeep', () => { // type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial } : T type Deep = { foo?: { @@ -30,11 +30,11 @@ describe('object utilities', () => { const optionsCopy: Settings = { xnumber2: 1, xstring2: 'x', xxx: 'newstring' }; const merged: Settings = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'x', xxx: 'newstring' }; - extend(settings, options); + assignDeep(settings, options); expect(settings).toEqual(merged); expect(options).toEqual(optionsCopy); - extend(settings, null, options); + assignDeep(settings, null, options); expect(settings).toEqual(merged); expect(options).toEqual(optionsCopy); @@ -43,7 +43,7 @@ describe('object utilities', () => { const deep2copy: Deep = { foo: { baz: true }, foo2: document }; const deepmerged: Deep = { foo: { bar: true, baz: true }, foo2: document }; - extend(deep1, deep2); + assignDeep(deep1, deep2); expect(deep1.foo).toEqual(deepmerged.foo); expect(deep2.foo).toEqual(deep2copy.foo); expect(deep1.foo2).toBe(document); @@ -51,21 +51,21 @@ describe('object utilities', () => { const arr = [1, 2, 3]; const nestedArray: NestedArray = { arr }; - expect(extend({}, nestedArray).arr).not.toBe(arr); - expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); - expect(Array.isArray(extend({ arr: {} }, nestedArray).arr)).toBeTruthy(); - expect(isPlainObject(extend({ arr }, { arr: {} }).arr)).toBeTruthy(); + expect(assignDeep({}, nestedArray).arr).not.toBe(arr); + expect(Array.isArray(assignDeep({ arr: {} }, nestedArray).arr)).toBeTruthy(); + expect(Array.isArray(assignDeep({ arr: {} }, nestedArray).arr)).toBeTruthy(); + expect(isPlainObject(assignDeep({ arr }, { arr: {} }).arr)).toBeTruthy(); let empty: { foo?: any } = {}; const optionsWithLength = { foo: { length: -1 } }; - extend(empty, optionsWithLength); + assignDeep(empty, optionsWithLength); expect(empty.foo).toEqual(optionsWithLength.foo); empty = {}; const optionsWithDate = { foo: { date: new Date() } }; - extend(empty, optionsWithDate); + assignDeep(empty, optionsWithDate); expect(empty.foo).toEqual(optionsWithDate.foo); /** @constructor */ @@ -75,53 +75,57 @@ describe('object utilities', () => { const optionsWithCustomObject = { foo: { date: customObject } }; empty = {}; - extend(empty, optionsWithCustomObject); + assignDeep(empty, optionsWithCustomObject); expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); // Makes the class a little more realistic MyKlass.prototype = { someMethod() {} }; empty = {}; - extend(empty, optionsWithCustomObject); + assignDeep(empty, optionsWithCustomObject); expect(empty.foo && empty.foo.date === customObject).toBeTruthy(); const MyNumber = Number; - let ret: any = extend({ foo: 4 }, { foo: new MyNumber(5) }); + let ret: any = assignDeep({ foo: 4 }, { foo: new MyNumber(5) }); expect(parseInt(ret.foo?.toString() as string, 10) === 5).toBeTruthy(); - let nullUndef = extend({}, options, { xnumber2: null }); + let nullUndef = assignDeep({}, options, { xnumber2: null }); expect(nullUndef.xnumber2).toBe(null); // @ts-ignore - nullUndef = extend({}, options, { xnumber2: undefined }); + nullUndef = assignDeep({}, options, {}); expect(nullUndef.xnumber2).toBe(options.xnumber2); // @ts-ignore - nullUndef = extend({}, options, { xnumber0: null }); + nullUndef = assignDeep({}, options, { xnumber2: undefined }); + expect(nullUndef.xnumber2).toBe(undefined); + + // @ts-ignore + nullUndef = assignDeep({}, options, { xnumber0: null }); expect(nullUndef.xnumber0).toBe(null); const target = {}; const recursive = { foo: target, bar: 5 }; - extend(target, recursive); + assignDeep(target, recursive); expect(target).toEqual({ bar: 5 }); - ret = extend({ foo: [] }, { foo: [0] }); + ret = assignDeep({ foo: [] }, { foo: [0] }); expect(ret.foo?.length).toBe(1); - ret = extend({ foo: '1,2,3' }, { foo: [1, 2, 3] }); + ret = assignDeep({ foo: '1,2,3' }, { foo: [1, 2, 3] }); expect(typeof ret.foo !== 'string').toBeTruthy(); - ret = extend({ foo: 'bar' }, { foo: null }); + ret = assignDeep({ foo: 'bar' }, { foo: null }); expect(typeof ret.foo !== 'undefined').toBeTruthy(); const obj = { foo: null }; - extend(obj, { foo: 'notnull' }); + assignDeep(obj, { foo: 'notnull' }); expect(obj.foo).toBe('notnull'); const func: { (): void; key?: string } = () => {}; - extend(func, { key: 'value' }); + assignDeep(func, { key: 'value' }); expect(func.key).toBe('value'); const defaults = { xnumber1: 5, xnumber2: 7, xstring1: 'peter', xstring2: 'pan' }; @@ -132,15 +136,15 @@ describe('object utilities', () => { const options2Copy = { xstring2: 'xx', xxx: 'newstringx' }; const merged2 = { xnumber1: 5, xnumber2: 1, xstring1: 'peter', xstring2: 'xx', xxx: 'newstringx' }; - settings = extend({}, defaults, options1, options2); + settings = assignDeep({}, defaults, options1, options2); expect(settings).toEqual(merged2); expect(defaults).toEqual(defaultsCopy); expect(options1).toEqual(options1Copy); expect(options2).toEqual(options2Copy); - expect(extend('', { foo: 1 })).toEqual({ foo: 1 }); - expect(extend(null, { foo: null, deep: { foo: null } })).toEqual({ foo: null, deep: { foo: null } }); - expect(extend(12, { foo: 1, deep: { foo: null, text: '' } })).toEqual({ foo: 1, deep: { foo: null, text: '' } }); + expect(assignDeep('', { foo: 1 })).toEqual({ foo: 1 }); + expect(assignDeep(null, { foo: null, deep: { foo: null } })).toEqual({ foo: null, deep: { foo: null } }); + expect(assignDeep(12, { foo: 1, deep: { foo: null, text: '' } })).toEqual({ foo: 1, deep: { foo: null, text: '' } }); }); });