improve domobserver code and tests

This commit is contained in:
Rene
2021-04-17 21:36:26 +02:00
parent 2d0daf8d16
commit 9a9aea5bfe
3 changed files with 75 additions and 37 deletions
@@ -28,7 +28,7 @@ interface DOMObserverOptionsBase {
}
interface DOMContentObserverOptions extends DOMObserverOptionsBase {
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname | function returning eventname]
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname(s) | function returning eventname(s)] -> eventnames divided by whitespaces
_nestedTargetSelector?: string;
_ignoreContentChange?: DOMObserverIgnoreContentChange; // function which will prevent marking certain dom changes as content change if it returns true
_ignoreNestedTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed on nested targets if it returns true
@@ -50,7 +50,7 @@ interface DOMContentObserver extends DOMObserverBase {
interface DOMTargetObserver extends DOMObserverBase {}
export type DOMObserverEventContentChange =
| Array<[StringNullUndefined, ((elms: Node[]) => string) | StringNullUndefined] | null | undefined>
| Array<[StringNullUndefined, ((elms: Node[]) => StringNullUndefined) | StringNullUndefined] | null | undefined>
| false
| null
| undefined;
@@ -90,22 +90,6 @@ export type DOMObserver<ContentObserver extends boolean> = ContentObserver exten
const createEventContentChange = (target: Element, eventContentChange: DOMObserverEventContentChange, callback: (...args: any) => any) => {
let map: Map<Node, string> | undefined;
let eventContentChangeRef: DOMObserverEventContentChange;
const addEvent = (elm: Node, eventName: string) => {
if (map) {
const entry = map.get(elm);
const newEntry = isUndefined(entry);
const changedExistingEntry = !newEntry && eventName !== entry;
const register = newEntry || changedExistingEntry;
if (changedExistingEntry) {
off(elm, entry!, callback);
}
if (register) {
map.set(elm, eventName);
on(elm, eventName, callback);
}
}
};
const _destroy = () => {
if (map) {
map.forEach((eventName: string, elm: Node) => off(elm, eventName, callback));
@@ -113,28 +97,37 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
}
};
const _updateElements = (getElements?: (selector: string) => Node[]) => {
if (eventContentChangeRef) {
if (map && eventContentChangeRef) {
const eventElmList = eventContentChangeRef.reduce<Array<[Node[], string]>>((arr, item) => {
if (item) {
const selector = item[0];
const eventName = item[1];
const elements = eventName && selector && (getElements ? getElements(selector) : find(selector, target));
const eventNames = item[1];
const elements = eventNames && selector && (getElements ? getElements(selector) : find(selector, target));
const parsedEventNames = isFunction(eventNames) ? eventNames(elements) : eventNames;
if (elements) {
push(arr, [elements, isFunction(eventName) ? eventName(elements) : eventName!], true);
if (elements && elements.length && parsedEventNames && isString(parsedEventNames)) {
push(arr, [elements, parsedEventNames.trim()], true);
}
}
return arr;
}, []);
each(eventElmList, (item) => {
const elements = item[0];
const eventName = item[1];
each(eventElmList, (item) =>
each(item[0], (elm) => {
const eventNames = item[1];
const registredEventNames = map!.get(elm);
const newEntry = isUndefined(registredEventNames);
const changingExistingEntry = !newEntry && eventNames !== registredEventNames;
const finalEventNames = changingExistingEntry ? `${registredEventNames} ${eventNames}` : eventNames;
each(elements, (elm) => {
addEvent(elm, eventName);
});
});
if (changingExistingEntry) {
off(elm, registredEventNames!, callback);
}
map!.set(elm, finalEventNames);
on(elm, finalEventNames, callback);
})
);
}
};
const _updateEventContentChange = (newEventContentChange: DOMObserverEventContentChange) => {
@@ -12,28 +12,28 @@ type RunEachItem = ((...args: any) => any | any[]) | null | undefined;
*/
export function each<T>(
array: Array<T> | ReadonlyArray<T>,
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | unknown
): Array<T> | ReadonlyArray<T>;
export function each<T>(
array: Array<T> | ReadonlyArray<T> | null | undefined,
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | unknown
): Array<T> | ReadonlyArray<T> | null | undefined;
export function each<T>(
arrayLikeObject: ArrayLike<T>,
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | unknown
): ArrayLike<T>;
export function each<T>(
arrayLikeObject: ArrayLike<T> | null | undefined,
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | unknown
): ArrayLike<T> | null | undefined;
export function each(obj: PlainObject, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void): PlainObject;
export function each(obj: PlainObject, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | unknown): PlainObject;
export function each(
obj: PlainObject | null | undefined,
callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void
callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | unknown
): PlainObject | null | undefined;
export function each<T>(
source: ArrayLike<T> | PlainObject | null | undefined,
callback: (value: T, indexOrKey: any, source: any) => boolean | void
callback: (value: T, indexOrKey: any, source: any) => boolean | unknown
): Array<T> | ReadonlyArray<T> | ArrayLike<T> | PlainObject | null | undefined {
if (isArrayLike(source)) {
for (let i = 0; i < source.length; i++) {
@@ -354,6 +354,8 @@ const addRemoveImgElmsFn = async () => {
before();
appendChildren(imgElmsSlot, imgHolder);
await timeout(250);
await waitForOrFailTest(() => {
after();
compare(2);
@@ -389,6 +391,8 @@ const addRemoveImgElmsFn = async () => {
addMultipleItem();
addMultipleItem();
await timeout(250);
await waitForOrFailTest(() => {
after();
compare(2);
@@ -403,6 +407,45 @@ const addRemoveImgElmsFn = async () => {
await addMultiple();
// remove load event from image test
const addChanged = async (
newEventContentChange: Array<[string | null | undefined, (() => string | null | undefined) | string | null | undefined] | null | undefined>
) => {
contentDomObserver._updateEventContentChange(newEventContentChange);
const img = new Image(1, 1);
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
const { before, after, compare } = changedThrough(domContentObserverObservations);
const imgHolder = createDiv('img');
appendChildren(imgHolder, img);
before();
appendChildren(imgElmsSlot, imgHolder);
await timeout(250);
await waitForOrFailTest(() => {
after();
compare(1);
});
contentDomObserver._updateEventContentChange(contentChangeArr);
};
await addChanged([
['img', 'something'],
['img', 'something2'],
['img', null],
['img', undefined],
['img', () => 'hi'],
['img', () => null],
['img', () => undefined],
null,
undefined,
]);
await addChanged([]);
removeElements(document.querySelectorAll('.img'));
await timeout(250);
@@ -578,11 +621,13 @@ const start = async () => {
targetDomObserver._update();
targetDomObserver._destroy();
targetDomObserver._destroy();
targetDomObserver._update();
contentDomObserver._updateEventContentChange([]);
contentDomObserver._update();
contentDomObserver._destroy();
contentDomObserver._destroy();
contentDomObserver._updateEventContentChange([]);
contentDomObserver._update();
};