code improvements

This commit is contained in:
Rene Haas
2021-01-13 17:13:25 +01:00
parent 9b47e9d76b
commit dc3b83ac7b
19 changed files with 323 additions and 98 deletions
@@ -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;
}
}