mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-26 00:54:07 +03:00
improve tests
This commit is contained in:
@@ -231,12 +231,11 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
||||
|
||||
const trinsicObserver = (_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||
const sizeObserver = createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: !_nativeScrollbarStyling });
|
||||
const hostMutationObserver = createDOMObserver(_host, onHostMutation, {
|
||||
const hostMutationObserver = createDOMObserver(_host, false, onHostMutation, {
|
||||
_styleChangingAttributes: attrs,
|
||||
_attributes: attrs,
|
||||
});
|
||||
const contentMutationObserver = createDOMObserver(_content || _viewport, onContentMutation, {
|
||||
_observeContent: true,
|
||||
const contentMutationObserver = createDOMObserver(_content || _viewport, true, onContentMutation, {
|
||||
_styleChangingAttributes: attrs,
|
||||
_attributes: attrs,
|
||||
_eventContentChange: options!.updating!.elementEvents as [string, string][],
|
||||
|
||||
@@ -17,37 +17,65 @@ import {
|
||||
} from 'support';
|
||||
|
||||
type StringNullUndefined = string | null | undefined;
|
||||
|
||||
type DOMContentObserverCallback = (contentChanged: boolean) => any;
|
||||
|
||||
type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
||||
|
||||
interface DOMObserverOptionsBase {
|
||||
_attributes?: string[];
|
||||
_styleChangingAttributes?: string[];
|
||||
}
|
||||
|
||||
interface DOMContentObserverOptions extends DOMObserverOptionsBase {
|
||||
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname | function returning eventname]
|
||||
_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
|
||||
}
|
||||
|
||||
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
||||
_ignoreTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed if it returns true
|
||||
}
|
||||
|
||||
interface DOMObserverBase {
|
||||
_destroy: () => void;
|
||||
_update: () => void;
|
||||
}
|
||||
|
||||
interface DOMContentObserver extends DOMObserverBase {
|
||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => void;
|
||||
}
|
||||
|
||||
interface DOMTargetObserver extends DOMObserverBase {}
|
||||
|
||||
export type DOMObserverEventContentChange =
|
||||
| Array<[StringNullUndefined, ((elms: Node[]) => string) | StringNullUndefined] | null | undefined>
|
||||
| false
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type DOMObserverIgnoreContentChange = (
|
||||
mutation: MutationRecord,
|
||||
isNestedTarget: boolean,
|
||||
domObserverTarget: HTMLElement,
|
||||
domObserverOptions: DOMObserverOptions | undefined
|
||||
domObserverOptions: DOMContentObserverOptions | undefined
|
||||
) => boolean;
|
||||
export type DOMObserverIgnoreTargetAttrChange = (
|
||||
|
||||
export type DOMObserverIgnoreTargetChange = (
|
||||
target: Node,
|
||||
attributeName: string,
|
||||
oldAttributeValue: string | null,
|
||||
newAttributeValue: string | null
|
||||
) => boolean;
|
||||
export interface DOMObserverOptions {
|
||||
_observeContent?: boolean; // do observe children and trigger content change
|
||||
_attributes?: string[]; // observed attributes
|
||||
_styleChangingAttributes?: string[]; // list of attributes that trigger a contentChange or a targetStyleChange if changed
|
||||
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname]
|
||||
_nestedTargetSelector?: string;
|
||||
_ignoreTargetAttrChange?: DOMObserverIgnoreTargetAttrChange;
|
||||
_ignoreContentChange?: DOMObserverIgnoreContentChange;
|
||||
}
|
||||
export interface DOMObserver {
|
||||
_destroy: () => void;
|
||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => void;
|
||||
_update: () => void;
|
||||
}
|
||||
|
||||
export type DOMObserverCallback<ContentObserver extends boolean> = ContentObserver extends true
|
||||
? DOMContentObserverCallback
|
||||
: DOMTargetObserverCallback;
|
||||
|
||||
export type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
||||
|
||||
export type DOMObserver<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserver : DOMTargetObserver;
|
||||
|
||||
// const styleChangingAttributes = ['id', 'class', 'style', 'open'];
|
||||
// const mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||
@@ -63,25 +91,26 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
|
||||
let map: Map<Node, string> | undefined;
|
||||
let eventContentChangeRef: DOMObserverEventContentChange;
|
||||
const addEvent = (elm: Node, eventName: string) => {
|
||||
const entry = map!.get(elm);
|
||||
const newEntry = isUndefined(entry);
|
||||
const registerEvent = () => {
|
||||
map!.set(elm, eventName);
|
||||
on(elm, eventName, callback);
|
||||
};
|
||||
if (map) {
|
||||
const entry = map.get(elm);
|
||||
const newEntry = isUndefined(entry);
|
||||
const changedExistingEntry = !newEntry && eventName !== entry;
|
||||
const register = newEntry || changedExistingEntry;
|
||||
|
||||
if (!newEntry && eventName !== entry) {
|
||||
off(elm, entry!, callback);
|
||||
registerEvent();
|
||||
} else if (newEntry) {
|
||||
registerEvent();
|
||||
if (changedExistingEntry) {
|
||||
off(elm, entry!, callback);
|
||||
}
|
||||
if (register) {
|
||||
map.set(elm, eventName);
|
||||
on(elm, eventName, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
const _destroy = () => {
|
||||
map!.forEach((eventName: string, elm: Node) => {
|
||||
off(elm, eventName, callback);
|
||||
});
|
||||
map!.clear();
|
||||
if (map) {
|
||||
map.forEach((eventName: string, elm: Node) => off(elm, eventName, callback));
|
||||
map.clear();
|
||||
}
|
||||
};
|
||||
const _updateElements = (getElements?: (selector: string) => Node[]) => {
|
||||
if (eventContentChangeRef) {
|
||||
@@ -127,37 +156,39 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a DOM observer which observes DOM changes to either the target element or its children. (not only direct children but also nested ones)
|
||||
* Creates a DOM observer which observes DOM changes to either the target element or its children.
|
||||
* @param target The element which shall be observed.
|
||||
* @param isContentObserver Whether this observer is just observing the target or just the targets children. (not only direct children but also nested ones)
|
||||
* @param callback The callback which gets called if a change was detected.
|
||||
* @param options The options for DOM change detection.
|
||||
* @returns A object which represents the instance of the DOM observer.
|
||||
*/
|
||||
export const createDOMObserver = (
|
||||
export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
target: HTMLElement,
|
||||
callback: (targetChangedAttrs: string[], targetStyleChanged: boolean, contentChanged: boolean) => any,
|
||||
options?: DOMObserverOptions
|
||||
): DOMObserver => {
|
||||
isContentObserver: ContentObserver,
|
||||
callback: DOMObserverCallback<ContentObserver>,
|
||||
options?: DOMObserverOptions<ContentObserver>
|
||||
): DOMObserver<ContentObserver> => {
|
||||
let isConnected = false;
|
||||
const {
|
||||
_observeContent,
|
||||
_attributes,
|
||||
_styleChangingAttributes,
|
||||
_eventContentChange,
|
||||
_nestedTargetSelector,
|
||||
_ignoreTargetAttrChange: _ignoreTargetChange,
|
||||
_ignoreTargetChange,
|
||||
_ignoreNestedTargetChange,
|
||||
_ignoreContentChange,
|
||||
} = options || {};
|
||||
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
||||
const {
|
||||
_destroy: destroyEventContentChange,
|
||||
_updateElements: updateEventContentChangeElements,
|
||||
_updateEventContentChange: updateEventContentChange,
|
||||
} = createEventContentChange(
|
||||
target,
|
||||
_observeContent && _eventContentChange,
|
||||
isContentObserver && _eventContentChange,
|
||||
debounce(() => {
|
||||
if (isConnected) {
|
||||
callback([], false, true);
|
||||
(callback as DOMContentObserverCallback)(true);
|
||||
}
|
||||
}, 84)
|
||||
);
|
||||
@@ -167,7 +198,7 @@ export const createDOMObserver = (
|
||||
const finalStyleChangingAttributes = _styleChangingAttributes || [];
|
||||
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
|
||||
const observerCallback = (mutations: MutationRecord[]) => {
|
||||
const ignoreTargetChange = _ignoreTargetChange || noop;
|
||||
const ignoreTargetChange = (isContentObserver ? _ignoreNestedTargetChange : _ignoreTargetChange) || noop;
|
||||
const ignoreContentChange = _ignoreContentChange || noop;
|
||||
const targetChangedAttrs: string[] = [];
|
||||
const totalAddedNodes: Node[] = [];
|
||||
@@ -181,19 +212,12 @@ export const createDOMObserver = (
|
||||
const targetIsMutationTarget = target === mutationTarget;
|
||||
const attributeValue = isAttributesType && isString(attributeName) ? attr(mutationTarget as HTMLElement, attributeName!) : 0;
|
||||
const attributeChanged = attributeValue !== 0 && oldValue !== attributeValue;
|
||||
const targetAttrChanged =
|
||||
attributeChanged &&
|
||||
targetIsMutationTarget &&
|
||||
!_observeContent &&
|
||||
!ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue as string | null);
|
||||
const styleChangingAttrChanged = indexOf(finalStyleChangingAttributes, attributeName) > -1 && attributeChanged;
|
||||
|
||||
if (targetAttrChanged) {
|
||||
push(targetChangedAttrs, attributeName!);
|
||||
}
|
||||
if (_observeContent) {
|
||||
// if is content observer and something changed in children
|
||||
if (isContentObserver && !targetIsMutationTarget) {
|
||||
const notOnlyAttrChanged = !isAttributesType;
|
||||
const contentAttrChanged = isAttributesType && styleChangingAttrChanged && !targetIsMutationTarget;
|
||||
const contentAttrChanged = isAttributesType && styleChangingAttrChanged;
|
||||
const isNestedTarget = contentAttrChanged && _nestedTargetSelector && is(mutationTarget, _nestedTargetSelector);
|
||||
const baseAssertion = isNestedTarget
|
||||
? !ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue as string | null)
|
||||
@@ -205,7 +229,11 @@ export const createDOMObserver = (
|
||||
contentChanged = contentChanged || contentFinalChanged;
|
||||
childListChanged = childListChanged || isChildListType;
|
||||
}
|
||||
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
|
||||
// else if is target observer and target attr changed
|
||||
else if (attributeChanged && !ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue as string | null)) {
|
||||
push(targetChangedAttrs, attributeName!);
|
||||
targetStyleChanged = targetStyleChanged || styleChangingAttrChanged;
|
||||
}
|
||||
});
|
||||
|
||||
if (childListChanged && !isEmptyArray(totalAddedNodes)) {
|
||||
@@ -217,8 +245,11 @@ export const createDOMObserver = (
|
||||
}, [])
|
||||
);
|
||||
}
|
||||
if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged || contentChanged) {
|
||||
callback(targetChangedAttrs, targetStyleChanged, contentChanged);
|
||||
|
||||
if (isContentObserver && contentChanged) {
|
||||
(callback as DOMContentObserverCallback)(contentChanged);
|
||||
} else if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
|
||||
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
|
||||
}
|
||||
};
|
||||
const mutationObserver: MutationObserver = new MutationObserverConstructor!(observerCallback);
|
||||
@@ -228,9 +259,9 @@ export const createDOMObserver = (
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: observedAttributes,
|
||||
subtree: _observeContent,
|
||||
childList: _observeContent,
|
||||
characterData: _observeContent,
|
||||
subtree: isContentObserver,
|
||||
childList: isContentObserver,
|
||||
characterData: isContentObserver,
|
||||
});
|
||||
isConnected = true;
|
||||
|
||||
@@ -243,12 +274,12 @@ export const createDOMObserver = (
|
||||
}
|
||||
},
|
||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => {
|
||||
updateEventContentChange(isConnected && _observeContent && newEventContentChange);
|
||||
updateEventContentChange(isConnected && isContentObserver && newEventContentChange);
|
||||
},
|
||||
_update: () => {
|
||||
if (isConnected) {
|
||||
observerCallback(mutationObserver.takeRecords());
|
||||
}
|
||||
},
|
||||
};
|
||||
} as DOMObserver<ContentObserver>;
|
||||
};
|
||||
|
||||
@@ -21,14 +21,14 @@ import {
|
||||
|
||||
import { createDOMObserver } from 'observers/domObserver';
|
||||
|
||||
interface DOMObserverResult {
|
||||
type DOMContentObserverResult = boolean;
|
||||
type DOMTargetObserverResult = {
|
||||
changedTargetAttrs: string[];
|
||||
styleChanged: boolean;
|
||||
contentChanged: boolean;
|
||||
}
|
||||
};
|
||||
interface SeparateChangeThrough {
|
||||
added?: DOMObserverResult[];
|
||||
removed?: DOMObserverResult[];
|
||||
added?: DOMContentObserverResult[];
|
||||
removed?: DOMContentObserverResult[];
|
||||
}
|
||||
|
||||
const targetChangesCountSlot: HTMLElement | null = document.querySelector('#targetChanges');
|
||||
@@ -68,23 +68,31 @@ const hostSelector = '.host';
|
||||
const ignorePrefix = 'ignore';
|
||||
const attrs = ['id', 'class', 'style', 'open'];
|
||||
const contentChangeArr: Array<[string, string | ((elms: Node[]) => string)]> = [['img', 'load']];
|
||||
const targetElmObservations: DOMObserverResult[] = [];
|
||||
const targetElmContentElmObservations: DOMObserverResult[] = [];
|
||||
const domTargetObserverObservations: DOMTargetObserverResult[] = [];
|
||||
const domContentObserverObservations: DOMContentObserverResult[] = [];
|
||||
|
||||
createDOMObserver(
|
||||
document.querySelector('#target') as HTMLElement,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => {
|
||||
targetElmObservations.push({ changedTargetAttrs, styleChanged, contentChanged });
|
||||
const targetDomObserver = createDOMObserver(
|
||||
document.querySelector('#target')!,
|
||||
false,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean) => {
|
||||
should.ok(Array.isArray(changedTargetAttrs), 'The changedTargetAttrs parameter in a target dom observer must be a array.');
|
||||
should.equal(typeof styleChanged, 'boolean', 'The styleChanged parameter in a target dom observer must be a boolean.');
|
||||
|
||||
if (styleChanged && changedTargetAttrs.length === 0) {
|
||||
should.ok(false, 'Style changing properties must always be inside the changedTargetAttrs array.');
|
||||
}
|
||||
|
||||
domTargetObserverObservations.push({ changedTargetAttrs, styleChanged });
|
||||
requestAnimationFrame(() => {
|
||||
if (targetChangesCountSlot) {
|
||||
targetChangesCountSlot.textContent = `${targetElmObservations.length}`;
|
||||
targetChangesCountSlot.textContent = `${domTargetObserverObservations.length}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
_styleChangingAttributes: attrs,
|
||||
_attributes: attrs.concat(['data-target']),
|
||||
_ignoreTargetAttrChange: (target, attrName, oldValue, newValue) => {
|
||||
_ignoreTargetChange: (target, attrName, oldValue, newValue) => {
|
||||
if (attrName === 'class' && oldValue && newValue) {
|
||||
const diff = diffClass(oldValue, newValue);
|
||||
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
||||
@@ -92,20 +100,35 @@ createDOMObserver(
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// @ts-ignore
|
||||
_ignoreContentChange: () => {
|
||||
// if param: isContentObserver = false, this function should never be called.
|
||||
should.ok(false, 'A target dom observer must not call the _ignoreContentChange method.');
|
||||
return true;
|
||||
},
|
||||
// @ts-ignore
|
||||
_ignoreNestedTargetChange: () => {
|
||||
// if param: isContentObserver = false, this function should never be called.
|
||||
should.ok(false, 'A target dom observer must not call the _ignoreNestedTargetChange method.');
|
||||
return true;
|
||||
},
|
||||
}
|
||||
);
|
||||
const { _updateEventContentChange } = createDOMObserver(
|
||||
document.querySelector('#target .content') as HTMLElement,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => {
|
||||
targetElmContentElmObservations.push({ changedTargetAttrs, styleChanged, contentChanged });
|
||||
|
||||
const contentDomObserver = createDOMObserver(
|
||||
document.querySelector('#target .content')!,
|
||||
true,
|
||||
(contentChanged: boolean) => {
|
||||
should.equal(typeof contentChanged, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
|
||||
|
||||
domContentObserverObservations.push(contentChanged);
|
||||
requestAnimationFrame(() => {
|
||||
if (contentChangesCountSlot) {
|
||||
contentChangesCountSlot.textContent = `${targetElmContentElmObservations.length}`;
|
||||
contentChangesCountSlot.textContent = `${domContentObserverObservations.length}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
_observeContent: true,
|
||||
_styleChangingAttributes: attrs,
|
||||
_attributes: attrs,
|
||||
_eventContentChange: contentChangeArr,
|
||||
@@ -114,7 +137,7 @@ const { _updateEventContentChange } = createDOMObserver(
|
||||
const { target, attributeName } = mutation;
|
||||
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
|
||||
},
|
||||
_ignoreTargetAttrChange: (target, attrName, oldValue, newValue) => {
|
||||
_ignoreNestedTargetChange: (target, attrName, oldValue, newValue) => {
|
||||
if (attrName === 'class' && oldValue && newValue) {
|
||||
const diff = diffClass(oldValue, newValue);
|
||||
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
||||
@@ -122,15 +145,23 @@ const { _updateEventContentChange } = createDOMObserver(
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// @ts-ignore
|
||||
_ignoreTargetChange: () => {
|
||||
// if param: isContentObserver = true, this function should never be called.
|
||||
should.ok(false, 'A content dom observer must not call the _ignoreTargetChange method.');
|
||||
return true;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const getTotalObservations = () => targetElmObservations.length + targetElmContentElmObservations.length;
|
||||
const getTotalObservations = () => domTargetObserverObservations.length + domContentObserverObservations.length;
|
||||
const getLast = <T>(arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T);
|
||||
const changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObserverResult[]) => {
|
||||
const changedThrough = <ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult>(
|
||||
observationLists?: Array<ChangeThrough[]> | ChangeThrough[]
|
||||
) => {
|
||||
interface Stat {
|
||||
total: number;
|
||||
lists: Array<[DOMObserverResult[], number]>;
|
||||
lists: Array<[ChangeThrough[], number]>;
|
||||
}
|
||||
const noObservationLists = observationLists === undefined;
|
||||
let before: Stat;
|
||||
@@ -139,13 +170,13 @@ const changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObser
|
||||
observationLists = [];
|
||||
}
|
||||
if (isArray(observationLists) && !isArray(observationLists[0])) {
|
||||
observationLists = [observationLists] as Array<DOMObserverResult[]>;
|
||||
observationLists = [observationLists] as Array<ChangeThrough[]>;
|
||||
}
|
||||
|
||||
const getStats = (): Stat => {
|
||||
return {
|
||||
total: getTotalObservations(),
|
||||
lists: (observationLists as Array<DOMObserverResult[]>).map((list) => [list, list.length]),
|
||||
lists: (observationLists as Array<ChangeThrough[]>).map((list) => [list, list.length]),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -156,7 +187,7 @@ const changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObser
|
||||
after: () => {
|
||||
after = getStats();
|
||||
},
|
||||
compare: (comparisonTableOrNumber: number | Map<DOMObserverResult[], number> = 0) => {
|
||||
compare: (comparisonTableOrNumber: number | Map<ChangeThrough[], number> = 0) => {
|
||||
let totalDiff = 0;
|
||||
if (isNumber(comparisonTableOrNumber) || noObservationLists) {
|
||||
before.lists.forEach((_, index) => {
|
||||
@@ -164,7 +195,11 @@ const changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObser
|
||||
const [, afterCount] = after.lists[index];
|
||||
|
||||
totalDiff += afterCount - beforeCount;
|
||||
should(afterCount).equal(beforeCount + (noObservationLists ? 0 : (comparisonTableOrNumber as number)));
|
||||
should.equal(
|
||||
afterCount,
|
||||
beforeCount + (noObservationLists ? 0 : (comparisonTableOrNumber as number)),
|
||||
'Before and after changes for a certain observer are correct. (number)'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
before.lists.forEach((_, index) => {
|
||||
@@ -172,10 +207,14 @@ const changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObser
|
||||
const [, afterCount] = after.lists[index];
|
||||
|
||||
totalDiff += afterCount - beforeCount;
|
||||
should(afterCount).equal(beforeCount + (comparisonTableOrNumber.get(list) || 0));
|
||||
should.equal(
|
||||
afterCount,
|
||||
beforeCount + (comparisonTableOrNumber.get(list) || 0),
|
||||
'Before and after changes for a certain observer are correct. (Map)'
|
||||
);
|
||||
});
|
||||
}
|
||||
should(after.total).equal(before.total + totalDiff);
|
||||
should.equal(after.total, before.total + totalDiff, 'Total changes are correct.');
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -188,10 +227,10 @@ const attrChangeListener = (attrChangeTarget: HTMLElement | null) =>
|
||||
isClass && target.classList.add('something');
|
||||
!isClass && target.setAttribute(selectedValue, 'something');
|
||||
});
|
||||
const iterateAttrChange = async (
|
||||
const iterateAttrChange = async <ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult>(
|
||||
select: HTMLSelectElement | null,
|
||||
changeThrough?: DOMObserverResult[],
|
||||
checkChange?: (observation: DOMObserverResult, selected: string) => any
|
||||
changeThrough?: ChangeThrough[],
|
||||
checkChange?: (observation: ChangeThrough, selected: string) => any
|
||||
) => {
|
||||
const { before, after, compare } = changedThrough(changeThrough);
|
||||
|
||||
@@ -214,10 +253,10 @@ const iterateAttrChange = async (
|
||||
},
|
||||
});
|
||||
};
|
||||
const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMObserverResult[] | SeparateChangeThrough) => {
|
||||
const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMContentObserverResult[] | SeparateChangeThrough) => {
|
||||
if (slot) {
|
||||
let addChangeThrough: DOMObserverResult[] | undefined = changeThrough as DOMObserverResult[] | undefined;
|
||||
let removeChangeThrough: DOMObserverResult[] | undefined = changeThrough as DOMObserverResult[] | undefined;
|
||||
let addChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as DOMContentObserverResult[] | undefined;
|
||||
let removeChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as DOMContentObserverResult[] | undefined;
|
||||
if (changeThrough && !isArray(changeThrough)) {
|
||||
addChangeThrough = (changeThrough as SeparateChangeThrough).added;
|
||||
removeChangeThrough = (changeThrough as SeparateChangeThrough).removed;
|
||||
@@ -236,11 +275,9 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMOb
|
||||
});
|
||||
|
||||
if (addChangeThrough) {
|
||||
const { contentChanged, styleChanged, changedTargetAttrs } = getLast(addChangeThrough);
|
||||
const contentChanged = getLast(addChangeThrough);
|
||||
await waitForOrFailTest(() => {
|
||||
should(contentChanged).equal(true);
|
||||
should(styleChanged).equal(false);
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
should.equal(contentChanged, true, 'Adding an content element must result in a content change.');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -259,10 +296,8 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMOb
|
||||
compare(1);
|
||||
|
||||
if (removeChangeThrough) {
|
||||
const { changedTargetAttrs, styleChanged, contentChanged } = getLast(removeChangeThrough);
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
should(styleChanged).equal(false);
|
||||
should(contentChanged).equal(true);
|
||||
const contentChanged = getLast(removeChangeThrough);
|
||||
should.equal(contentChanged, true, 'Removing an content element must result in a content change.');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -277,7 +312,7 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMOb
|
||||
await removeElm();
|
||||
}
|
||||
};
|
||||
const triggerSummaryElemet = async (summaryElm: HTMLElement | null, changeThrough?: DOMObserverResult[]) => {
|
||||
const triggerSummaryElemet = async (summaryElm: HTMLElement | null, changeThrough?: DOMContentObserverResult[]) => {
|
||||
// onyl do if summary is working (IE. exception)
|
||||
if (summaryElm && (summaryElm.nextElementSibling as HTMLElement)?.offsetHeight === 0) {
|
||||
const click = async () => {
|
||||
@@ -302,17 +337,17 @@ const addRemoveTargetElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetElmsSlot);
|
||||
};
|
||||
const addRemoveTargetContentElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetContentElmsSlot, targetElmContentElmObservations);
|
||||
await addRemoveElementsTest(targetContentElmsSlot, domContentObserverObservations);
|
||||
};
|
||||
const addRemoveTargetContentBetweenElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetContentBetweenElmsSlot, targetElmContentElmObservations);
|
||||
await addRemoveElementsTest(targetContentBetweenElmsSlot, domContentObserverObservations);
|
||||
};
|
||||
const addRemoveImgElmsFn = 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 { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||
const imgHolder = createDiv('img');
|
||||
appendChildren(imgHolder, img);
|
||||
|
||||
@@ -323,15 +358,11 @@ const addRemoveImgElmsFn = async () => {
|
||||
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 previousContentChanged = getLast(domContentObserverObservations, 1);
|
||||
should.equal(previousContentChanged, true, 'Adding an content image must result in a content change.');
|
||||
|
||||
const eventObservation = getLast(targetElmContentElmObservations);
|
||||
should(eventObservation.contentChanged).equal(true);
|
||||
should(eventObservation.styleChanged).equal(false);
|
||||
should(eventObservation.changedTargetAttrs.length).equal(0);
|
||||
const lastContentChanged = getLast(domContentObserverObservations);
|
||||
should.equal(lastContentChanged, true, 'The images load event must result in a content change.');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -341,7 +372,7 @@ const addRemoveImgElmsFn = async () => {
|
||||
|
||||
// test event content change debounce
|
||||
const addMultiple = async () => {
|
||||
const { before, after, compare } = changedThrough(targetElmContentElmObservations);
|
||||
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||
const addMultipleItem = () => {
|
||||
const img = new Image(1, 1);
|
||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
||||
@@ -362,15 +393,11 @@ const addRemoveImgElmsFn = async () => {
|
||||
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 previousContentChanged = getLast(domContentObserverObservations, 1);
|
||||
should.equal(previousContentChanged, true, 'Adding mutliple content images must result in a single content change. (debounced)');
|
||||
|
||||
const eventObservation = getLast(targetElmContentElmObservations);
|
||||
should(eventObservation.contentChanged).equal(true);
|
||||
should(eventObservation.styleChanged).equal(false);
|
||||
should(eventObservation.changedTargetAttrs.length).equal(0);
|
||||
const lastContentChanged = getLast(domContentObserverObservations);
|
||||
should.equal(lastContentChanged, true, 'Multiple images load events must result in a single cintent change. (debounced)');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -384,7 +411,7 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
const startTransition = async (elm: Element, expectTransitionEndContentChange: boolean) => {
|
||||
await timeout(50); // time for css to apply class a bit later to trigger transition
|
||||
|
||||
const { before: beforeTransition, after: afterTransition, compare: compareTransition } = changedThrough(targetElmContentElmObservations);
|
||||
const { before: beforeTransition, after: afterTransition, compare: compareTransition } = changedThrough(domContentObserverObservations);
|
||||
beforeTransition();
|
||||
removeClass(elm, 'resetTransition'); // IE...
|
||||
addClass(elm, 'active');
|
||||
@@ -398,10 +425,8 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
afterTransition();
|
||||
compareTransition(expectTransitionEndContentChange ? 2 : 1); // 2 because 1: added class mutation and 2: transition end event
|
||||
|
||||
const eventObservation = getLast(targetElmContentElmObservations);
|
||||
should(eventObservation.contentChanged).equal(true);
|
||||
should(eventObservation.styleChanged).equal(false);
|
||||
should(eventObservation.changedTargetAttrs.length).equal(0);
|
||||
const contentChanged = getLast(domContentObserverObservations);
|
||||
should.equal(contentChanged, true, 'The transitionend event must trigger a event content change.');
|
||||
resolve(1);
|
||||
});
|
||||
},
|
||||
@@ -414,7 +439,7 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
};
|
||||
const add = async (expectTransitionEndContentChange: boolean) => {
|
||||
const elm = createDiv(`transition ${expectTransitionEndContentChange ? 'highlight' : ''}`);
|
||||
const { before, after, compare } = changedThrough(targetElmContentElmObservations);
|
||||
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||
|
||||
before();
|
||||
|
||||
@@ -424,14 +449,12 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
after();
|
||||
compare(1);
|
||||
|
||||
const eventObservation = getLast(targetElmContentElmObservations);
|
||||
should(eventObservation.contentChanged).equal(true);
|
||||
should(eventObservation.styleChanged).equal(false);
|
||||
should(eventObservation.changedTargetAttrs.length).equal(0);
|
||||
const contentChanged = getLast(domContentObserverObservations);
|
||||
should.equal(contentChanged, true, 'Adding an content element (transition) must result in a content change.');
|
||||
});
|
||||
|
||||
await startTransition(elm, expectTransitionEndContentChange && true);
|
||||
_updateEventContentChange(contentChangeArr);
|
||||
contentDomObserver._updateEventContentChange(contentChangeArr);
|
||||
await startTransition(elm, expectTransitionEndContentChange && false);
|
||||
|
||||
removeElements(elm);
|
||||
@@ -441,13 +464,13 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
|
||||
await add(false);
|
||||
|
||||
_updateEventContentChange(
|
||||
contentDomObserver._updateEventContentChange(
|
||||
contentChangeArr.concat([
|
||||
[
|
||||
'.transition',
|
||||
(elms) => {
|
||||
elms.forEach((elm) => {
|
||||
should(hasClass(elm as Element, 'transition')).equal(true);
|
||||
should.equal(hasClass(elm as Element, 'transition'), true, 'Every checked element must match the correpsonding selector.'); // in this case "".transition"
|
||||
});
|
||||
return 'transitionend';
|
||||
},
|
||||
@@ -458,7 +481,10 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
await add(true);
|
||||
};
|
||||
const ignoreTargetChangeFn = async () => {
|
||||
const check = async (target: Element | null, changeThrough: DOMObserverResult[]) => {
|
||||
const check = async <ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult>(
|
||||
target: Element | null,
|
||||
changeThrough: ChangeThrough[]
|
||||
) => {
|
||||
const { before, after, compare } = changedThrough(changeThrough);
|
||||
before();
|
||||
|
||||
@@ -473,24 +499,25 @@ const ignoreTargetChangeFn = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
await check(targetElm, targetElmObservations);
|
||||
await check(targetElmContentElm, targetElmContentElmObservations);
|
||||
await check(targetElm, domTargetObserverObservations);
|
||||
await check(targetElmContentElm, domContentObserverObservations);
|
||||
};
|
||||
const iterateTargetAttrChange = async () => {
|
||||
await iterateAttrChange(setTargetAttr, targetElmObservations, (observation, selected) => {
|
||||
const { changedTargetAttrs, styleChanged, contentChanged } = observation;
|
||||
should(changedTargetAttrs.includes(selected)).equal(true);
|
||||
should(styleChanged).equal(true);
|
||||
should(contentChanged).equal(false);
|
||||
await iterateAttrChange(setTargetAttr, domTargetObserverObservations, (observation, selected) => {
|
||||
const { changedTargetAttrs, styleChanged } = observation;
|
||||
should.equal(
|
||||
changedTargetAttrs.includes(selected),
|
||||
true,
|
||||
'A attribute change on the target element for a DOMTargetObserver must be inside the changedTargetAttrs array.'
|
||||
);
|
||||
should.equal(styleChanged, true, 'A style changing attribute on the target element for a DOMTargetObserver must set styleChanged to true.');
|
||||
});
|
||||
await iterateAttrChange(setFilteredTargetAttr);
|
||||
};
|
||||
const iterateContentAttrChange = async () => {
|
||||
await iterateAttrChange(setContentAttr, targetElmContentElmObservations, (observation) => {
|
||||
const { changedTargetAttrs, styleChanged, contentChanged } = observation;
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
should(styleChanged).equal(false);
|
||||
should(contentChanged).equal(true);
|
||||
await iterateAttrChange(setContentAttr, domContentObserverObservations, (observation) => {
|
||||
const contentChanged = observation;
|
||||
should.equal(contentChanged, true, 'A attribute change inside the content must trigger a content change for a DOMContentObserver.');
|
||||
});
|
||||
await iterateAttrChange(setFilteredContentAttr);
|
||||
};
|
||||
@@ -499,16 +526,14 @@ const iterateContentBetweenAttrChange = async () => {
|
||||
await iterateAttrChange(setFilteredContentBetweenAttr);
|
||||
};
|
||||
const iterateContentHostElmAttrChange = async () => {
|
||||
await iterateAttrChange(setContentHostElmAttr, targetElmContentElmObservations, (observation) => {
|
||||
const { changedTargetAttrs, styleChanged, contentChanged } = observation;
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
should(styleChanged).equal(false);
|
||||
should(contentChanged).equal(true);
|
||||
await iterateAttrChange(setContentHostElmAttr, domContentObserverObservations, (observation) => {
|
||||
const contentChanged = observation;
|
||||
should.equal(contentChanged, true, 'A attribute change for a nested target must trigger a content change for a DOMContentObserver.');
|
||||
});
|
||||
await iterateAttrChange(setFilteredContentHostElmAttr);
|
||||
};
|
||||
const triggerContentSummaryChange = async () => {
|
||||
await triggerSummaryElemet(summaryContent, targetElmContentElmObservations);
|
||||
await triggerSummaryElemet(summaryContent, domContentObserverObservations);
|
||||
};
|
||||
const triggerBetweenSummaryChange = async () => {
|
||||
await triggerSummaryElemet(summaryBetween);
|
||||
@@ -550,6 +575,16 @@ const start = async () => {
|
||||
await addRemoveImgElmsFn();
|
||||
|
||||
setTestResult(true);
|
||||
|
||||
targetDomObserver._update();
|
||||
targetDomObserver._destroy();
|
||||
targetDomObserver._update();
|
||||
|
||||
contentDomObserver._updateEventContentChange([]);
|
||||
contentDomObserver._update();
|
||||
contentDomObserver._destroy();
|
||||
contentDomObserver._updateEventContentChange([]);
|
||||
contentDomObserver._update();
|
||||
};
|
||||
|
||||
startBtn?.addEventListener('click', start);
|
||||
|
||||
@@ -90,21 +90,27 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
|
||||
// no overflow if not needed
|
||||
if (targetElm && newContentSize.w > 0) {
|
||||
should.ok(observerElm.getBoundingClientRect().right <= targetElm.getBoundingClientRect().right);
|
||||
should.ok(
|
||||
observerElm.getBoundingClientRect().right <= targetElm.getBoundingClientRect().right,
|
||||
'Generated observer element inst overflowing target element. (width)'
|
||||
);
|
||||
}
|
||||
if (targetElm && newContentSize.h > 0) {
|
||||
should.ok(observerElm.getBoundingClientRect().bottom <= targetElm.getBoundingClientRect().bottom);
|
||||
should.ok(
|
||||
observerElm.getBoundingClientRect().bottom <= targetElm.getBoundingClientRect().bottom,
|
||||
'Generated observer element inst overflowing target element. (height)'
|
||||
);
|
||||
}
|
||||
|
||||
if (dimensions && (offsetSizeChanged || contentSizeChanged || dirChanged)) {
|
||||
await waitForOrFailTest(() => {
|
||||
if (offsetSizeChanged || contentSizeChanged) {
|
||||
should.equal(sizeIterations, currSizeIterations + 1);
|
||||
should.equal(sizeIterations, currSizeIterations + 1, 'Size change was detected correctly.');
|
||||
}
|
||||
if (dirChanged) {
|
||||
const expectedCacheValue = newDir === 'rtl';
|
||||
should.equal(directionIterations, currDirectionIterations + 1);
|
||||
should.equal(sizeObserver._getCurrentCacheValues()._directionIsRTL._value, expectedCacheValue);
|
||||
should.equal(directionIterations, currDirectionIterations + 1, 'Direction change was detected correctly.');
|
||||
should.equal(sizeObserver._getCurrentCacheValues()._directionIsRTL._value, expectedCacheValue, 'Direction cache value is correct.');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -158,8 +164,8 @@ const start = async () => {
|
||||
|
||||
console.log('init direction changes:', directionIterations);
|
||||
console.log('init size changes:', sizeIterations);
|
||||
should.ok(directionIterations > 0);
|
||||
should.ok(sizeIterations > 0);
|
||||
should.ok(directionIterations > 0, 'Initial direction observations are fired.');
|
||||
should.ok(sizeIterations > 0, 'Initial size observations are fired.');
|
||||
|
||||
targetElm?.removeAttribute('style');
|
||||
await iterateDisplay();
|
||||
@@ -176,7 +182,7 @@ const start = async () => {
|
||||
});
|
||||
|
||||
sizeObserver._destroy();
|
||||
should.equal(targetElm?.children.length, preInitChildren);
|
||||
should.equal(targetElm?.children.length, preInitChildren, 'Destruction removes all generated elements.');
|
||||
setTestResult(true);
|
||||
};
|
||||
|
||||
|
||||
@@ -65,9 +65,13 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
if (trinsicHeightChanged) {
|
||||
should.equal(heightIterations, currHeightIterations + 1);
|
||||
should.equal(heightIterations, currHeightIterations + 1, 'Height intrinsic change has been detected correctly.');
|
||||
}
|
||||
should.equal(trinsicObserver._getCurrentCacheValues()._heightIntrinsic._value, newHeightIntrinsic);
|
||||
should.equal(
|
||||
trinsicObserver._getCurrentCacheValues()._heightIntrinsic._value,
|
||||
newHeightIntrinsic,
|
||||
'Height intrinsic cache value is correct.'
|
||||
);
|
||||
});
|
||||
},
|
||||
afterEach,
|
||||
@@ -93,7 +97,7 @@ const changeWhileHidden = async () => {
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, false);
|
||||
should.equal(heightIntrinsic, false, 'Trinsic sizing changes while hidden from intrinsic to extrinsic.');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -107,7 +111,7 @@ const changeWhileHidden = async () => {
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, true);
|
||||
should.equal(heightIntrinsic, true, 'Trinsic sizing changes while hidden from extrinsic to intrinsic.');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -129,7 +133,7 @@ const start = async () => {
|
||||
await changeWhileHidden();
|
||||
|
||||
trinsicObserver._destroy();
|
||||
should.equal(targetElm?.children.length, preInitChildren);
|
||||
should.equal(targetElm?.children.length, preInitChildren, 'After destruction all generated elements are removed.');
|
||||
setTestResult(true);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user