diff --git a/packages/overlayscrollbars/src/autoUpdateLoop/autoUpdateLoop.ts b/packages/overlayscrollbars/src/autoUpdateLoop/autoUpdateLoop.ts index 8e86797..1efa2be 100644 --- a/packages/overlayscrollbars/src/autoUpdateLoop/autoUpdateLoop.ts +++ b/packages/overlayscrollbars/src/autoUpdateLoop/autoUpdateLoop.ts @@ -1,4 +1,4 @@ -import { rAF, cAF, isEmptyArray, indexOf, createCache, runEach } from 'support'; +import { rAF, cAF, isEmptyArray, indexOf, createCache, runEach, push } from 'support'; import { getEnvironment } from 'environment'; export interface AutoUpdateLoop { @@ -40,7 +40,7 @@ const createAutoUpdateLoop = (): AutoUpdateLoop => { function interval(newInterval: number): () => void; function interval(newInterval?: number): number | (() => void) { if (newInterval) { - intervals.push(newInterval); + push(intervals, newInterval); updateLoopInterval(); return () => { @@ -53,7 +53,7 @@ const createAutoUpdateLoop = (): AutoUpdateLoop => { return { _add: (fn) => { - loopFunctions.push(fn); + push(loopFunctions, fn); if (!loopIsRunning && !isEmptyArray(loopFunctions)) { getEnvironment()._autoUpdateLoop = loopIsRunning = true; diff --git a/packages/overlayscrollbars/src/observers/domObserver.ts b/packages/overlayscrollbars/src/observers/domObserver.ts index 5f971de..15cee6d 100644 --- a/packages/overlayscrollbars/src/observers/domObserver.ts +++ b/packages/overlayscrollbars/src/observers/domObserver.ts @@ -1,4 +1,4 @@ -import { each, indexOf, isString, MutationObserverConstructor, isEmptyArray, on, off, attr, is, find } from 'support'; +import { each, indexOf, isString, MutationObserverConstructor, isEmptyArray, on, off, attr, is, find, push } from 'support'; type StringNullUndefined = string | null | undefined; @@ -47,7 +47,7 @@ export const createDOMObserver = ( const elements = eventName && selector && getElements(selector); if (elements) { - arr.push([eventName!, elements]); + push(arr, [eventName!, elements], true); } } return arr; @@ -85,7 +85,7 @@ export const createDOMObserver = ( targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged); if (targetAttrChanged) { - targetChangedAttrs.push(attributeName!); + push(targetChangedAttrs, attributeName!); } if (_observeContent) { const notOnlyAttrChanged = !isAttributesType; @@ -93,9 +93,7 @@ export const createDOMObserver = ( const contentFinalChanged = (notOnlyAttrChanged || contentAttrChanged) && (_ignoreContentChange ? !_ignoreContentChange(mutation, target, options) : _observeContent); - each(addedNodes, (node) => { - totalAddedNodes.push(node); - }); + push(totalAddedNodes, addedNodes); contentChanged = contentChanged || contentFinalChanged; childListChanged = childListChanged || isChildListType; @@ -103,7 +101,15 @@ export const createDOMObserver = ( }); if (childListChanged && !isEmptyArray(totalAddedNodes)) { - refreshEventContentChange((selector) => totalAddedNodes.filter((node) => is(node as Element, selector))); + refreshEventContentChange((selector) => + totalAddedNodes.reduce((arr, node) => { + push(arr, find(selector, node)); + if (is(node, selector)) { + push(arr, node); + } + return arr; + }, []) + ); } if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged || contentChanged) { callback(targetChangedAttrs, targetStyleChanged, contentChanged); @@ -123,7 +129,7 @@ export const createDOMObserver = ( isConnected = true; if (_observeContent) { - refreshEventContentChange((selector) => find(selector, target) as Node[]); + refreshEventContentChange((selector) => find(selector, target)); } return { diff --git a/packages/overlayscrollbars/src/observers/sizeObserver.ts b/packages/overlayscrollbars/src/observers/sizeObserver.ts index 95bab03..5ed8f1e 100644 --- a/packages/overlayscrollbars/src/observers/sizeObserver.ts +++ b/packages/overlayscrollbars/src/observers/sizeObserver.ts @@ -16,6 +16,7 @@ import { addClass, isString, equalWH, + push, cAF, rAF, ResizeObserverConstructor, @@ -61,7 +62,7 @@ export const createSizeObserver = ( if (ResizeObserverConstructor) { const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallbackProxy); resizeObserverInstance.observe(listenerElement); - offListeners.push(() => resizeObserverInstance.disconnect()); + push(offListeners, () => resizeObserverInstance.disconnect()); } else { const observerElementChildren = createDOM( `
` @@ -111,8 +112,7 @@ export const createSizeObserver = ( return false; }; - offListeners.push(on(expandElement, scrollEventName, onScroll)); - offListeners.push(on(shrinkElement, scrollEventName, onScroll)); + push(offListeners, [on(expandElement, scrollEventName, onScroll), on(shrinkElement, scrollEventName, onScroll)]); // lets assume that the divs will never be that large and a constant value is enough style(expandElementChild, { @@ -125,7 +125,8 @@ export const createSizeObserver = ( if (direction) { const updateDirectionCache = createCache(() => getDirection(sizeObserver)); - offListeners.push( + push( + offListeners, on(sizeObserver, scrollEventName, (event: Event) => { const directionCache = updateDirectionCache(); const { _value, _changed } = directionCache; @@ -148,7 +149,7 @@ export const createSizeObserver = ( // appearCallback is always needed on scroll-observer strategy to reset it if (appearCallback) { addClass(sizeObserver, classNameSizeObserverAppear); - offListeners.push(on(sizeObserver, animationStartEventName, appearCallback)); + push(offListeners, on(sizeObserver, animationStartEventName, appearCallback)); } prependChildren(target, sizeObserver); diff --git a/packages/overlayscrollbars/src/observers/trinsicObserver.ts b/packages/overlayscrollbars/src/observers/trinsicObserver.ts index 354c764..ec17524 100644 --- a/packages/overlayscrollbars/src/observers/trinsicObserver.ts +++ b/packages/overlayscrollbars/src/observers/trinsicObserver.ts @@ -1,4 +1,15 @@ -import { WH, Cache, createDOM, offsetSize, runEach, prependChildren, removeElements, createCache, IntersectionObserverConstructor } from 'support'; +import { + WH, + Cache, + createDOM, + offsetSize, + runEach, + prependChildren, + removeElements, + createCache, + push, + IntersectionObserverConstructor, +} from 'support'; import { createSizeObserver } from 'observers/sizeObserver'; import { classNameTrinsicObserver } from 'classnames'; @@ -35,9 +46,10 @@ export const createTrinsicObserver = ( { root: target } ); intersectionObserverInstance.observe(trinsicObserver); - offListeners.push(() => intersectionObserverInstance.disconnect()); + push(offListeners, () => intersectionObserverInstance.disconnect()); } else { - offListeners.push( + push( + offListeners, createSizeObserver(trinsicObserver, () => { const newSize = offsetSize(trinsicObserver); const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize); diff --git a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts index 61eab4f..d076dfd 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/OverlayScrollbars.ts @@ -1,6 +1,6 @@ import { OSTarget, OSTargetObject, CSSDirection } from 'typings'; import { createStructureLifecycle } from 'lifecycles/structureLifecycle'; -import { Cache, appendChildren, addClass, contents, is, isHTMLElement, createDiv, each } from 'support'; +import { Cache, appendChildren, addClass, contents, is, isHTMLElement, createDiv, each, push } from 'support'; import { createSizeObserver } from 'observers/sizeObserver'; import { createTrinsicObserver } from 'observers/trinsicObserver'; import { Lifecycle } from 'lifecycles/lifecycleBase'; @@ -44,7 +44,7 @@ const OverlayScrollbars = (target: OSTarget, options?: any, extensions?: any): v const lifecycles: Lifecycle[] = []; const { host } = osTarget; - lifecycles.push(createStructureLifecycle(osTarget)); + push(lifecycles, createStructureLifecycle(osTarget)); // eslint-disable-next-line const onSizeChanged = (directionCache?: Cache) => { diff --git a/packages/overlayscrollbars/src/support/dom/events.ts b/packages/overlayscrollbars/src/support/dom/events.ts index 8f0e9ab..4e7ee94 100644 --- a/packages/overlayscrollbars/src/support/dom/events.ts +++ b/packages/overlayscrollbars/src/support/dom/events.ts @@ -1,4 +1,4 @@ -import { each, runEach } from 'support/utils/array'; +import { each, push, runEach } from 'support/utils/array'; let passiveEventsSupport: boolean; const supportPassiveEvents = (): boolean => { @@ -69,7 +69,7 @@ export const on = (target: EventTarget, eventNames: string, listener: EventListe } : listener; - offListeners.push(off.bind(null, target, eventName, finalListener, capture)); + push(offListeners, off.bind(null, target, eventName, finalListener, capture)); target.addEventListener(eventName, finalListener, nativeOptions); }); diff --git a/packages/overlayscrollbars/src/support/dom/traversal.ts b/packages/overlayscrollbars/src/support/dom/traversal.ts index 6a52161..e0afab1 100644 --- a/packages/overlayscrollbars/src/support/dom/traversal.ts +++ b/packages/overlayscrollbars/src/support/dom/traversal.ts @@ -1,6 +1,7 @@ -import { each, from } from 'support/utils/array'; +import { isElement } from 'support/utils/types'; +import { push, from } from 'support/utils/array'; -type InputElementType = Element | null | undefined; +type InputElementType = Element | Node | null | undefined; type OutputElementType = Element | null; const elmPrototype = Element.prototype; @@ -12,12 +13,9 @@ const elmPrototype = Element.prototype; */ const find = (selector: string, elm?: InputElementType): Element[] => { const arr: Array = []; + const rootElm = elm ? (isElement(elm) ? elm : null) : document; - each((elm || document).querySelectorAll(selector), (e: Element) => { - arr.push(e); - }); - - return arr; + return rootElm ? push(arr, rootElm.querySelectorAll(selector)) : arr; }; /** @@ -25,7 +23,11 @@ const find = (selector: string, elm?: InputElementType): Element[] => { * @param selector The selector which has to be searched by. * @param elm The element from which the search shall be outgoing. */ -const findFirst = (selector: string, elm?: InputElementType): OutputElementType => (elm || document).querySelector(selector); +const findFirst = (selector: string, elm?: InputElementType): OutputElementType => { + const rootElm = elm ? (isElement(elm) ? elm : null) : document; + + return rootElm ? rootElm.querySelector(selector) : null; +}; /** * Determines whether the passed element is matching with the passed selector. @@ -33,12 +35,12 @@ const findFirst = (selector: string, elm?: InputElementType): OutputElementType * @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'. */ const is = (elm: InputElementType, selector: string): boolean => { - if (elm) { + if (isElement(elm)) { /* istanbul ignore next */ // eslint-disable-next-line // @ts-ignore - const fn = elmPrototype.matches || elmPrototype.msMatchesSelector; - return fn && fn.call(elm, selector); + const fn: (...args: any) => boolean = elmPrototype.matches || elmPrototype.msMatchesSelector; + return fn.call(elm, selector); } return false; }; @@ -51,17 +53,12 @@ const is = (elm: InputElementType, selector: string): boolean => { const children = (elm: InputElementType, selector?: string): ReadonlyArray => { const childs: Array = []; - each(elm && elm.children, (child: Element) => { - if (selector) { - if (is(child, selector)) { - childs.push(child); - } - } else { - childs.push(child); - } - }); - - return childs; + return isElement(elm) + ? push( + childs, + from(elm.children).filter((child) => (selector ? is(child, selector) : child)) + ) + : childs; }; /** @@ -77,7 +74,7 @@ const contents = (elm: InputElementType): ReadonlyArray => (elm ? fro const parent = (elm: InputElementType): OutputElementType => (elm ? elm.parentElement : null); const closest = (elm: InputElementType, selector: string): OutputElementType => { - if (elm) { + if (isElement(elm)) { // eslint-disable-next-line // @ts-ignore if (elmPrototype.closest) { @@ -85,7 +82,7 @@ const closest = (elm: InputElementType, selector: string): OutputElementType => } do { if (is(elm, selector)) { - return elm; + return elm as Element; } elm = parent(elm); } while (elm !== null && elm.nodeType === 1); diff --git a/packages/overlayscrollbars/src/support/options/validation.ts b/packages/overlayscrollbars/src/support/options/validation.ts index be9a79b..dd6c5f1 100644 --- a/packages/overlayscrollbars/src/support/options/validation.ts +++ b/packages/overlayscrollbars/src/support/options/validation.ts @@ -1,5 +1,5 @@ -import { each, hasOwnProperty, keys } from 'support/utils'; -import { type, isArray, isUndefined, isEmptyObject, isPlainObject, isString } from 'support/utils/types'; +import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils'; +import { type, isArray, isUndefined, isPlainObject, isString } from 'support/utils/types'; import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidationResult, OptionsValidated } from 'support/options'; import { PlainObject } from 'typings'; @@ -93,13 +93,13 @@ const validateRecursive = ( isValid = !!enumStringSplit.find((possibility) => possibility === optionsValue); // build error message - errorEnumStrings.push(...enumStringSplit); + push(errorEnumStrings, enumStringSplit); } else { isValid = optionsTemplateTypes[optionsValueType] === currTemplateType; } // build error message - errorPossibleTypes.push(isEnumString ? optionsTemplateTypes.string : typeString!); + push(errorPossibleTypes, isEnumString ? optionsTemplateTypes.string : typeString!); // continue if invalid, break if valid return !isValid; diff --git a/packages/overlayscrollbars/src/support/utils/array.ts b/packages/overlayscrollbars/src/support/utils/array.ts index 95021de..e3ef0b1 100644 --- a/packages/overlayscrollbars/src/support/utils/array.ts +++ b/packages/overlayscrollbars/src/support/utils/array.ts @@ -1,4 +1,4 @@ -import { isArrayLike } from 'support/utils/types'; +import { isArrayLike, isString } from 'support/utils/types'; import { PlainObject } from 'typings'; type RunEachItem = ((...args: any) => any | any[]) | null | undefined; @@ -55,6 +55,16 @@ export function each( */ export const indexOf = (arr: Array, item: T, fromIndex?: number): number => arr.indexOf(item, fromIndex); +/** + * Pushesh all given items into the given array and returns it. + * @param array The array the items shall be pushed into. + * @param items The items which shall be pushed into the array. + */ +export const push = (array: Array, items: T | ArrayLike, arrayIsSingleItem?: boolean): Array => { + !arrayIsSingleItem && !isString(items) && isArrayLike(items) ? Array.prototype.push.apply(array, items as Array) : array.push(items as T); + return array; +}; + /** * 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. @@ -65,7 +75,7 @@ export const from = (arr: ArrayLike) => { } const result: Array = []; each(arr, (elm) => { - result.push(elm); + push(result, elm); }); return result; }; diff --git a/packages/overlayscrollbars/src/support/utils/object.ts b/packages/overlayscrollbars/src/support/utils/object.ts index 46416bf..cf6c57d 100644 --- a/packages/overlayscrollbars/src/support/utils/object.ts +++ b/packages/overlayscrollbars/src/support/utils/object.ts @@ -72,3 +72,14 @@ export function assignDeep( // Return the modified object return target as any; } + +/** + * Returns true if the given object is empty, false otherwise. + * @param obj The Object. + */ +export function isEmptyObject(obj: any): boolean { + /* eslint-disable no-restricted-syntax, guard-for-in */ + for (const name in obj) return false; + return true; + /* eslint-enable */ +} diff --git a/packages/overlayscrollbars/src/support/utils/types.ts b/packages/overlayscrollbars/src/support/utils/types.ts index 74c2813..53e303c 100644 --- a/packages/overlayscrollbars/src/support/utils/types.ts +++ b/packages/overlayscrollbars/src/support/utils/types.ts @@ -1,4 +1,7 @@ import { PlainObject } from 'typings'; +import { hasOwnProperty } from 'support/utils/object'; + +const ElementNodeType = Node.ELEMENT_NODE; export const type: (obj: any) => string = (obj) => { if (obj === undefined) return `${obj}`; @@ -47,7 +50,9 @@ export function isObject(obj: any): boolean { */ export function isArrayLike(obj: any): obj is ArrayLike { const length = !!obj && obj.length; - return isArray(obj) || (!isFunction(obj) && isNumber(length) && length > -1 && length % 1 == 0); // eslint-disable-line eqeqeq + const lengthCorrectFormat = isNumber(length) && length > -1 && length % 1 == 0; // eslint-disable-line eqeqeq + + return isArray(obj) || (!isFunction(obj) && lengthCorrectFormat) ? (length > 0 && isObject(obj) ? length - 1 in obj : true) : false; } /** @@ -58,12 +63,13 @@ export function isPlainObject(obj: any): obj is PlainObject { if (!obj || !isObject(obj) || type(obj) !== 'object') return false; let key; - const proto = 'prototype'; - const { hasOwnProperty } = Object[proto]; - const hasOwnConstructor = hasOwnProperty.call(obj, 'constructor'); - const hasIsPrototypeOf = obj.constructor && obj.constructor[proto] && hasOwnProperty.call(obj.constructor[proto], 'isPrototypeOf'); + const cstr = 'constructor'; + const ctor = obj[cstr]; + const ctorProto = ctor && ctor.prototype; + const hasOwnConstructor = hasOwnProperty(obj, cstr); + const hasIsPrototypeOf = ctorProto && hasOwnProperty(ctorProto, 'isPrototypeOf'); - if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + if (ctor && !hasOwnConstructor && !hasIsPrototypeOf) { return false; } @@ -73,7 +79,7 @@ export function isPlainObject(obj: any): obj is PlainObject { } /* eslint-enable */ - return isUndefined(key) || hasOwnProperty.call(obj, key); + return isUndefined(key) || hasOwnProperty(obj, key); } /** @@ -81,18 +87,15 @@ export function isPlainObject(obj: any): obj is PlainObject { * @param obj The object which shall be checked. */ export function isHTMLElement(obj: any): obj is HTMLElement { - const instaceOfRightHandSide = window.HTMLElement; - const doInstanceOf = isObject(instaceOfRightHandSide) || isFunction(instaceOfRightHandSide); - return !!(doInstanceOf ? obj instanceof instaceOfRightHandSide : obj && isObject(obj) && obj.nodeType === 1 && isString(obj.nodeName)); + const instanceofObj = window.HTMLElement; + return obj ? (instanceofObj ? obj instanceof instanceofObj : obj.nodeType === ElementNodeType) : false; } /** - * Returns true if the given object is empty, false otherwise. - * @param obj The Object. + * Checks whether the given object is a Element. + * @param obj The object which shall be checked. */ -export function isEmptyObject(obj: any): boolean { - /* eslint-disable no-restricted-syntax, guard-for-in */ - for (const name in obj) return false; - return true; - /* eslint-enable */ +export function isElement(obj: any): obj is Element { + const instanceofObj = window.Element; + return obj ? (instanceofObj ? obj instanceof instanceofObj : obj.nodeType === ElementNodeType) : false; } diff --git a/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts b/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts index f7685a4..7286ef4 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/dom/style.test.ts @@ -1,4 +1,5 @@ -import { isString, isPlainObject, isEmptyObject } from 'support/utils/types'; +import { isEmptyObject } from 'support/utils/object'; +import { isString, isPlainObject } from 'support/utils/types'; import { style, hide, show, topRightBottomLeft } from 'support/dom/style'; describe('dom style', () => { diff --git a/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts b/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts index e5da87f..7227bc2 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts @@ -1,4 +1,4 @@ -import { find, findFirst, is, children, contents, parent, createDiv, liesBetween } from 'support/dom'; +import { find, findFirst, is, children, contents, parent, createDiv, liesBetween, createDOM } from 'support/dom'; const slotElm = document.body; const testHTML = '

2

abc'; @@ -55,6 +55,10 @@ describe('dom traversal', () => { expect(nonExistent.length).toBe(0); }); + + test('text node', () => { + expect(find('div', createDOM('
textnodehere
')[0].firstChild)).toEqual([]); + }); }); describe('findFirst', () => { @@ -96,6 +100,10 @@ describe('dom traversal', () => { expect(nonExistent).toBe(null); }); + + test('text node', () => { + expect(findFirst('div', createDOM('
textnodehere
')[0].firstChild)).toEqual(null); + }); }); describe('is', () => { @@ -147,6 +155,10 @@ describe('dom traversal', () => { expect(is(null, '.div-class')).toBe(false); expect(is(null, '.other-class')).toBe(false); }); + + test('text node', () => { + expect(is(createDOM('
textnodehere
')[0].firstChild, '.hi')).toEqual(false); + }); }); describe('children', () => { @@ -169,6 +181,10 @@ describe('dom traversal', () => { expect(childs.length).toBe(1); expect(childs[0]).toBe(findFirst('input')); }); + + test('text node', () => { + expect(children(createDOM('
textnodehere
')[0].firstChild, '.hi')).toEqual([]); + }); }); describe('contents', () => { @@ -184,6 +200,10 @@ describe('dom traversal', () => { expect(childs.length).toEqual(0); }); + + test('text node', () => { + expect(contents(createDOM('
textnodehere
')[0].firstChild)).toEqual([]); + }); }); describe('parent', () => { @@ -198,6 +218,10 @@ describe('dom traversal', () => { expect(p).toBeNull(); }); + + test('text node', () => { + expect(parent(createDOM('
textnodehere
')[0].firstChild)?.nodeName).toEqual('DIV'); + }); }); describe('liesBetween', () => { @@ -286,5 +310,9 @@ describe('dom traversal', () => { Element.prototype.closest = original; }); + + test('text node', () => { + expect(liesBetween(createDOM('
textnodehere
')[0].firstChild, '.a', '.b')).toEqual(false); + }); }); }); diff --git a/packages/overlayscrollbars/tests/jsdom/support/utils/arrays.test.ts b/packages/overlayscrollbars/tests/jsdom/support/utils/arrays.test.ts index 3a21ffc..9860a65 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/utils/arrays.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/utils/arrays.test.ts @@ -1,6 +1,87 @@ -import { each, from, indexOf, runEach } from 'support/utils/array'; +import { push, each, from, indexOf, runEach } from 'support/utils/array'; describe('array utilities', () => { + describe('push', () => { + describe('single value', () => { + test('string', () => { + const arr: string[] = []; + const item = 'hi there'; + + expect(push(arr, item)).toBe(arr); + + expect(arr).toHaveLength(1); + expect(arr[0]).toBe(item); + }); + + test('array like', () => { + const arr: string[] = []; + const item = ['tuple', 'elem']; + + expect(push(arr, item, true)).toBe(arr); + + expect(arr).toHaveLength(1); + expect(arr[0]).toBe(item); + }); + + test('array like fake', () => { + const arr: any[] = []; + const item = { length: 2 }; + + expect(push(arr, item)).toBe(arr); + + expect(arr).toHaveLength(1); + expect(arr[0]).toBe(item); + }); + }); + + describe('multiple values', () => { + test('string', () => { + const arr: string[] = []; + const items = 'hi there'.split(''); + + expect(push(arr, items)).toBe(arr); + + expect(arr).toHaveLength(items.length); + expect(arr).toEqual(items); + }); + + test('array', () => { + const arr: string[] = []; + const items = ['tuple', 'elem']; + + expect(push(arr, items)).toBe(arr); + + expect(arr).toHaveLength(2); + expect(arr[0]).toBe('tuple'); + expect(arr[1]).toBe('elem'); + }); + + test('array like', () => { + const arr: string[] = []; + const items = { 0: 'zero', 1: 'one', 2: 'two', length: 3 }; + + expect(push(arr, items)).toBe(arr); + + expect(arr).toHaveLength(3); + expect(arr[0]).toBe('zero'); + expect(arr[1]).toBe('one'); + expect(arr[2]).toBe('two'); + }); + + test('array like query selector', () => { + document.body.innerHTML = '

testtext

'; + const arr: Node[] = []; + const items = document.querySelectorAll('*'); + + expect(push(arr, items)).toBe(arr); + + expect(arr).not.toHaveLength(0); + arr.forEach((node) => { + expect(node instanceof window.Element).toBe(true); + }); + }); + }); + }); describe('each', () => { describe('each through Array', () => { test('returns input', () => { @@ -170,6 +251,13 @@ describe('array utilities', () => { expect(testFunc).toBeCalledTimes(arrLikeObj.length - 1); }); }); + + describe('each through nothing', () => { + test('returns input', () => { + expect(each(null, () => {})).toBe(null); + expect(each(undefined, () => {})).toBe(undefined); + }); + }); }); describe('from', () => { diff --git a/packages/overlayscrollbars/tests/jsdom/support/utils/object.test.ts b/packages/overlayscrollbars/tests/jsdom/support/utils/object.test.ts index ab8ca83..da7a5dd 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/utils/object.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/utils/object.test.ts @@ -1,4 +1,4 @@ -import { assignDeep, keys, hasOwnProperty } from 'support/utils/object'; +import { assignDeep, keys, hasOwnProperty, isEmptyObject } from 'support/utils/object'; import { isPlainObject } from 'support/utils/types'; describe('object utilities', () => { @@ -164,6 +164,20 @@ describe('object utilities', () => { }); }); + describe('isEmptyObject', () => { + test('empty object is empty', () => { + expect(isEmptyObject({})).toBe(true); + }); + + test('filled object is not empty', () => { + expect(isEmptyObject({ a: 1, b: 2 })).toBe(false); + }); + + test('created object is empty', () => { + expect(isEmptyObject(Object.create(null))).toBe(true); + }); + }); + test('hasOwnProperty', () => { const obj = { a: 1, diff --git a/packages/overlayscrollbars/tests/jsdom/support/utils/types.test.ts b/packages/overlayscrollbars/tests/jsdom/support/utils/types.test.ts index 052e254..31eddd2 100644 --- a/packages/overlayscrollbars/tests/jsdom/support/utils/types.test.ts +++ b/packages/overlayscrollbars/tests/jsdom/support/utils/types.test.ts @@ -1,3 +1,4 @@ +import { createDOM } from 'support/dom/create'; import { type, isNumber, @@ -10,7 +11,7 @@ import { isNull, isArrayLike, isPlainObject, - isEmptyObject, + isElement, isHTMLElement, } from 'support/utils/types'; @@ -46,6 +47,7 @@ const typeNameValueMap = { window, body: document.body, querySelectorAll: document.querySelectorAll('*'), + textNode: createDOM('
textnodehere
')[0].firstChild, }; const testTypeFn = (typeFunc: Function, expectedTypeNameValueResultMap: any) => { @@ -130,6 +132,7 @@ describe('types', () => { document: true, window: true, body: true, + textNode: true, querySelectorAll: true, functionConstructor: true, arrayLikeObject: true, @@ -177,30 +180,29 @@ describe('types', () => { }); }); - test('isEmptyObject', () => { - testTypeFn(isEmptyObject, { - objectEmpty: true, - objectCreate: true, - arrayEmpty: true, + test('isElement', () => { + const temp = window.Element; - newNumber: true, - newBoolean: true, - newFunction: true, - newArray: true, - - null: true, - undefined: true, - booleanTrue: true, - booleanFalse: true, - void0: true, - number: true, - infinity: true, - functionConstructor: true, - function: true, - functionAsync: true, - functionArrow: true, - functionArrowAsync: true, + testTypeFn(isElement, { + body: true, }); + Array.from(document.querySelectorAll('*')).forEach((elm) => { + expect(isElement(elm)).toBeTruthy(); + }); + + // @ts-ignore + delete window.Element; + // @ts-ignore + window.Element = null; + + testTypeFn(isElement, { + body: true, + }); + Array.from(document.querySelectorAll('*')).forEach((elm) => { + expect(isElement(elm)).toBeTruthy(); + }); + + window.Element = temp; }); test('isHTMLElement', () => { @@ -213,6 +215,7 @@ describe('types', () => { expect(isHTMLElement(elm)).toBeTruthy(); }); + // @ts-ignore delete window.HTMLElement; // @ts-ignore window.HTMLElement = null; diff --git a/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.browser.ts b/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.browser.ts index 0852535..c01a966 100644 --- a/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.browser.ts @@ -28,10 +28,12 @@ const contentHostElmAttrChange: HTMLElement | null = document.querySelector('#co const targetElmsSlot = document.querySelector('#target .host-nest-item'); const targetContentElmsSlot = document.querySelector('#target .content .content-nest'); const targetContentBetweenElmsSlot = document.querySelector('#content-host'); +const imgElmsSlot = document.querySelector('#target .content-nest'); const addRemoveTargetElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetElms'); const addRemoveTargetContentElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentElms'); const addRemoveTargetContentBetweenElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentBetweenElms'); +const addImgElms: HTMLButtonElement | null = document.querySelector('#addImgElms'); const setTargetAttr: HTMLSelectElement | null = document.querySelector('#setTargetAttr'); const setFilteredTargetAttr: HTMLSelectElement | null = document.querySelector('#setFilteredTargetAttr'); const setContentAttr: HTMLSelectElement | null = document.querySelector('#setContentAttr'); @@ -48,7 +50,7 @@ const startBtn: HTMLButtonElement | null = document.querySelector('#start'); const targetElmObservations: DOMObserverResult[] = []; const targetElmContentElmObservations: DOMObserverResult[] = []; const getTotalObservations = () => targetElmObservations.length + targetElmContentElmObservations.length; -const getLast = (arr: T[]): T => arr[arr.length - 1] || ({} as T); +const getLast = (arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T); const changedThrough = (observationLists?: Array | DOMObserverResult[]) => { interface Stat { total: number; @@ -229,6 +231,37 @@ const addRemoveTargetContentElmsFn = async () => { const addRemoveTargetContentBetweenElmsFn = async () => { await addRemoveElementsTest(targetContentBetweenElmsSlot, targetElmContentElmObservations); }; +const addImgElmsFn = async () => { + const add = async () => { + const img = new Image(1, 1); + img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; + const { before, after, compare } = changedThrough(targetElmContentElmObservations); + const imgHolder = createDiv('img'); + appendChildren(imgHolder, img); + + before(); + appendChildren(imgElmsSlot, imgHolder); + + await waitForOrFailTest(() => { + after(); + compare(2); + + const mutationObserverObservation = getLast(targetElmContentElmObservations, 1); + should(mutationObserverObservation.contentChanged).equal(true); + should(mutationObserverObservation.styleChanged).equal(false); + should(mutationObserverObservation.changedTargetAttrs.length).equal(0); + + const eventObservation = getLast(targetElmContentElmObservations); + should(eventObservation.contentChanged).equal(true); + should(eventObservation.styleChanged).equal(false); + should(eventObservation.changedTargetAttrs.length).equal(0); + }); + }; + + await add(); + await add(); + await add(); +}; const iterateTargetAttrChange = async () => { await iterateAttrChange(setTargetAttr, targetElmObservations, (observation, selected) => { const { changedTargetAttrs, styleChanged, contentChanged } = observation; @@ -270,6 +303,7 @@ const triggerBetweenSummaryChange = async () => { addRemoveTargetElms?.addEventListener('click', addRemoveTargetElmsFn); addRemoveTargetContentElms?.addEventListener('click', addRemoveTargetContentElmsFn); addRemoveTargetContentBetweenElms?.addEventListener('click', addRemoveTargetContentBetweenElmsFn); +addImgElms?.addEventListener('click', addImgElmsFn); setTargetAttr?.addEventListener('change', attrChangeListener(targetElm)); setFilteredTargetAttr?.addEventListener('change', attrChangeListener(targetElm)); setContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange)); @@ -305,6 +339,9 @@ createDOMObserver( }, { _observeContent: true, + _eventContentChange: () => { + return [['img', 'load']]; + }, _ignoreContentChange: (mutation) => { const { target, attributeName } = mutation; return attributeName ? !hasClass(target as Element, 'host') && liesBetween(target as Element, '.host', '.content') : false; @@ -315,6 +352,8 @@ createDOMObserver( const start = async () => { setTestResult(null); + await addImgElmsFn(); + await addRemoveTargetElmsFn(); await addRemoveTargetContentElmsFn(); await addRemoveTargetContentBetweenElmsFn(); diff --git a/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.html b/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.html index c398ebe..dea7829 100644 --- a/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.html +++ b/packages/overlayscrollbars/tests/puppeteer/observers/domObserver/index.html @@ -2,6 +2,7 @@ +