mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-17 09:29:40 +03:00
code improvements
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<Node[]>((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 {
|
||||
|
||||
@@ -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(
|
||||
`<div class="${classNameSizeObserverListenerItem}" dir="ltr"><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}"></div></div><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}" style="width: 200%; height: 200%"></div></div></div>`
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<any>[] = [];
|
||||
const { host } = osTarget;
|
||||
|
||||
lifecycles.push(createStructureLifecycle(osTarget));
|
||||
push(lifecycles, createStructureLifecycle(osTarget));
|
||||
|
||||
// eslint-disable-next-line
|
||||
const onSizeChanged = (directionCache?: Cache<CSSDirection>) => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<Element> = [];
|
||||
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<Element> => {
|
||||
const childs: Array<Element> = [];
|
||||
|
||||
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<ChildNode> => (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);
|
||||
|
||||
@@ -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 = <T extends PlainObject>(
|
||||
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;
|
||||
|
||||
@@ -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<T>(
|
||||
*/
|
||||
export const indexOf = <T = any>(arr: Array<T>, 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 = <T>(array: Array<T>, items: T | ArrayLike<T>, arrayIsSingleItem?: boolean): Array<T> => {
|
||||
!arrayIsSingleItem && !isString(items) && isArrayLike(items) ? Array.prototype.push.apply(array, items as Array<T>) : 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 = <T = any>(arr: ArrayLike<T>) => {
|
||||
}
|
||||
const result: Array<T> = [];
|
||||
each(arr, (elm) => {
|
||||
result.push(elm);
|
||||
push(result, elm);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -72,3 +72,14 @@ export function assignDeep<T, U, V, W, X, Y, Z>(
|
||||
// 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 */
|
||||
}
|
||||
|
||||
@@ -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<T extends PlainObject = any>(obj: any): obj is ArrayLike<T> {
|
||||
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<T = any>(obj: any): obj is PlainObject<T> {
|
||||
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<T = any>(obj: any): obj is PlainObject<T> {
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
return isUndefined(key) || hasOwnProperty.call(obj, key);
|
||||
return isUndefined(key) || hasOwnProperty(obj, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,18 +87,15 @@ export function isPlainObject<T = any>(obj: any): obj is PlainObject<T> {
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 = '<div id="parent" class="div-class"><div id="child" class="div-class"></div></div><p>2</p><input type="text" value="3"></input>abc';
|
||||
@@ -55,6 +55,10 @@ describe('dom traversal', () => {
|
||||
|
||||
expect(nonExistent.length).toBe(0);
|
||||
});
|
||||
|
||||
test('text node', () => {
|
||||
expect(find('div', createDOM('<div>textnodehere</div>')[0].firstChild)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findFirst', () => {
|
||||
@@ -96,6 +100,10 @@ describe('dom traversal', () => {
|
||||
|
||||
expect(nonExistent).toBe(null);
|
||||
});
|
||||
|
||||
test('text node', () => {
|
||||
expect(findFirst('div', createDOM('<div>textnodehere</div>')[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('<div>textnodehere</div>')[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('<div>textnodehere</div>')[0].firstChild, '.hi')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('contents', () => {
|
||||
@@ -184,6 +200,10 @@ describe('dom traversal', () => {
|
||||
|
||||
expect(childs.length).toEqual(0);
|
||||
});
|
||||
|
||||
test('text node', () => {
|
||||
expect(contents(createDOM('<div>textnodehere</div>')[0].firstChild)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parent', () => {
|
||||
@@ -198,6 +218,10 @@ describe('dom traversal', () => {
|
||||
|
||||
expect(p).toBeNull();
|
||||
});
|
||||
|
||||
test('text node', () => {
|
||||
expect(parent(createDOM('<div>textnodehere</div>')[0].firstChild)?.nodeName).toEqual('DIV');
|
||||
});
|
||||
});
|
||||
|
||||
describe('liesBetween', () => {
|
||||
@@ -286,5 +310,9 @@ describe('dom traversal', () => {
|
||||
|
||||
Element.prototype.closest = original;
|
||||
});
|
||||
|
||||
test('text node', () => {
|
||||
expect(liesBetween(createDOM('<div>textnodehere</div>')[0].firstChild, '.a', '.b')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = '<div><p>testtext<h1></h1></p><div></div></div>';
|
||||
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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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('<div>textnodehere</div>')[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;
|
||||
|
||||
@@ -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 = <T>(arr: T[]): T => arr[arr.length - 1] || ({} as T);
|
||||
const getLast = <T>(arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T);
|
||||
const changedThrough = (observationLists?: Array<DOMObserverResult[]> | 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();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<button id="addRemoveTargetElms">Target Elements</button>
|
||||
<button id="addRemoveTargetContentElms">Content Elements</button>
|
||||
<button id="addRemoveTargetContentBetweenElms">Content Between Elements</button>
|
||||
<button id="addImgElms">Image Elements</button>
|
||||
|
||||
<label for="setTargetAttr">setTargetAttr</label>
|
||||
<select name="setTargetAttr" id="setTargetAttr">
|
||||
|
||||
@@ -75,3 +75,14 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
background: lime;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
img {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user