mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 11:40:37 +03:00
improve pptr dev experience and write dom observer tests
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
import { each, indexOf, isString, MutationObserverConstructor, isEmptyArray, liesBetween } from 'support';
|
||||
import { classNameHost, classNameContent } from 'classnames';
|
||||
import { each, indexOf, isString, MutationObserverConstructor, isEmptyArray, on, off, attr, is, find } from 'support';
|
||||
|
||||
type StringNullUndefined = string | null | undefined;
|
||||
|
||||
export type DOMOvserverEventContentChangeResult = Array<[StringNullUndefined, StringNullUndefined] | null | undefined>; // [selector, eventname]
|
||||
export type DOMOvserverEventContentChange = () => DOMOvserverEventContentChangeResult;
|
||||
export type DOMObserverIgnoreContentChange = (
|
||||
mutation: MutationRecord,
|
||||
domObserverTarget: HTMLElement,
|
||||
domObserverOptions: DOMObserverOptions | undefined
|
||||
) => boolean | null | undefined;
|
||||
export interface DOMObserverOptions {
|
||||
_observeContent?: boolean;
|
||||
_attributes?: string[];
|
||||
_ignoreContentChange?: DOMObserverIgnoreContentChange;
|
||||
_eventContentChange?: DOMOvserverEventContentChange;
|
||||
}
|
||||
export interface DOMObserver {
|
||||
_disconnect: () => void;
|
||||
@@ -12,76 +22,115 @@ export interface DOMObserver {
|
||||
|
||||
const styleChangingAttributes = ['id', 'class', 'style', 'open'];
|
||||
const mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||
|
||||
const isUnknownMutation = (
|
||||
attributeName: string | null,
|
||||
type: MutationRecordType,
|
||||
observeContent?: boolean,
|
||||
target?: Node,
|
||||
mutationTarget?: Node
|
||||
) => {
|
||||
const isAttributesType = type === 'attributes';
|
||||
const targetIsMutationTarget = target === mutationTarget;
|
||||
const styleChangingAttrChanged = indexOf(styleChangingAttributes, attributeName) > -1;
|
||||
const contentChanged = observeContent && !isAttributesType;
|
||||
const contentAttrChanged =
|
||||
observeContent &&
|
||||
isAttributesType &&
|
||||
styleChangingAttrChanged &&
|
||||
!targetIsMutationTarget &&
|
||||
!liesBetween(mutationTarget as Element | undefined, `.${classNameHost}`, `.${classNameContent}`);
|
||||
const targetAttrChanged = isAttributesType && styleChangingAttrChanged && targetIsMutationTarget && !observeContent;
|
||||
|
||||
return contentChanged || contentAttrChanged || targetAttrChanged;
|
||||
};
|
||||
const getAttributeChanged = (mutationTarget: Node, attributeName: string, oldValue: string | null): boolean =>
|
||||
oldValue !== attr(mutationTarget as HTMLElement, attributeName);
|
||||
|
||||
export const createDOMObserver = (
|
||||
target: HTMLElement,
|
||||
callback: (changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => any,
|
||||
callback: (targetChangedAttrs: string[], targetStyleChanged: boolean, contentChanged: boolean) => any,
|
||||
options?: DOMObserverOptions
|
||||
): DOMObserver => {
|
||||
const { _observeContent, _attributes } = options || {};
|
||||
let isConnected = false;
|
||||
const { _observeContent, _attributes, _ignoreContentChange, _eventContentChange } = options || {};
|
||||
const eventContentChangeCallback = () => {
|
||||
if (isConnected) {
|
||||
callback([], false, true);
|
||||
}
|
||||
};
|
||||
const refreshEventContentChange = (getElements: (selector: string) => Node[]) => {
|
||||
if (_eventContentChange) {
|
||||
const eventContentChanges = _eventContentChange();
|
||||
const eventElmList = eventContentChanges.reduce<Array<[string, Node[]]>>((arr, item) => {
|
||||
if (item) {
|
||||
const selector = item[0];
|
||||
const eventName = item[1];
|
||||
const elements = eventName && selector && getElements(selector);
|
||||
|
||||
if (elements) {
|
||||
arr.push([eventName!, elements]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
each(eventElmList, (item) => {
|
||||
const eventName = item[0];
|
||||
const elements = item[1];
|
||||
|
||||
each(elements, (elm) => {
|
||||
off(elm, eventName, eventContentChangeCallback);
|
||||
on(elm, eventName, eventContentChangeCallback);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// MutationObserver
|
||||
const observedAttributes = (_attributes || []).concat(_observeContent ? styleChangingAttributes : mutationObserverAttrsTextarea);
|
||||
const observedAttributes = (_attributes || []).concat(styleChangingAttributes); // TODO: observer textarea attrs if textarea
|
||||
const observerCallback = (mutations: MutationRecord[]) => {
|
||||
let styleChanged = false;
|
||||
const targetChangedAttrs: string[] = [];
|
||||
const totalAddedNodes: Node[] = [];
|
||||
let targetStyleChanged = false;
|
||||
let contentChanged = false;
|
||||
const changedTargetAttrs: string[] = [];
|
||||
let childListChanged = false;
|
||||
each(mutations, (mutation) => {
|
||||
const { attributeName, target: mutationTarget, type } = mutation;
|
||||
const { attributeName, target: mutationTarget, type, oldValue, addedNodes } = mutation;
|
||||
const isAttributesType = type === 'attributes';
|
||||
const isChildListType = type === 'childList';
|
||||
const targetIsMutationTarget = target === mutationTarget;
|
||||
const attributeChanged = isAttributesType && isString(attributeName) && getAttributeChanged(mutationTarget, attributeName!, oldValue);
|
||||
const targetAttrChanged = attributeChanged && targetIsMutationTarget && !_observeContent;
|
||||
const styleChangingAttrChanged = indexOf(styleChangingAttributes, attributeName) > -1 && attributeChanged;
|
||||
|
||||
styleChanged = styleChanged || isUnknownMutation(attributeName, type);
|
||||
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
|
||||
|
||||
if (_observeContent) {
|
||||
contentChanged = contentChanged || isUnknownMutation(attributeName, type, true, target, mutationTarget);
|
||||
if (targetAttrChanged) {
|
||||
targetChangedAttrs.push(attributeName!);
|
||||
}
|
||||
if (isString(attributeName) && target === mutationTarget) {
|
||||
changedTargetAttrs.push(attributeName);
|
||||
if (_observeContent) {
|
||||
const notOnlyAttrChanged = !isAttributesType;
|
||||
const contentAttrChanged = isAttributesType && styleChangingAttrChanged && !targetIsMutationTarget;
|
||||
const contentFinalChanged =
|
||||
(notOnlyAttrChanged || contentAttrChanged) && (_ignoreContentChange ? !_ignoreContentChange(mutation, target, options) : _observeContent);
|
||||
|
||||
each(addedNodes, (node) => {
|
||||
totalAddedNodes.push(node);
|
||||
});
|
||||
|
||||
contentChanged = contentChanged || contentFinalChanged;
|
||||
childListChanged = childListChanged || isChildListType;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isEmptyArray(changedTargetAttrs) || styleChanged || contentChanged) {
|
||||
callback(changedTargetAttrs, styleChanged, contentChanged);
|
||||
if (childListChanged && !isEmptyArray(totalAddedNodes)) {
|
||||
refreshEventContentChange((selector) => totalAddedNodes.filter((node) => is(node as Element, selector)));
|
||||
}
|
||||
if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged || contentChanged) {
|
||||
callback(targetChangedAttrs, targetStyleChanged, contentChanged);
|
||||
}
|
||||
};
|
||||
const mutationObserver: MutationObserver = new MutationObserverConstructor!(observerCallback);
|
||||
|
||||
const connect = () => {
|
||||
mutationObserver.observe(target, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
subtree: _observeContent,
|
||||
childList: _observeContent,
|
||||
characterData: _observeContent,
|
||||
attributeFilter: observedAttributes,
|
||||
});
|
||||
};
|
||||
mutationObserver.observe(target, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
attributeFilter: observedAttributes,
|
||||
subtree: _observeContent,
|
||||
childList: _observeContent,
|
||||
characterData: _observeContent,
|
||||
});
|
||||
|
||||
connect();
|
||||
isConnected = true;
|
||||
|
||||
if (_observeContent) {
|
||||
refreshEventContentChange((selector) => find(selector, target) as Node[]);
|
||||
}
|
||||
|
||||
return {
|
||||
_disconnect: mutationObserver.disconnect,
|
||||
_disconnect: () => {
|
||||
mutationObserver.disconnect();
|
||||
isConnected = false;
|
||||
},
|
||||
_update: () => {
|
||||
observerCallback(mutationObserver.takeRecords());
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ const elmPrototype = Element.prototype;
|
||||
* @param selector The selector which has to be searched by.
|
||||
* @param elm The element from which the search shall be outgoing.
|
||||
*/
|
||||
const find = (selector: string, elm?: InputElementType): ReadonlyArray<Element> => {
|
||||
const find = (selector: string, elm?: InputElementType): Element[] => {
|
||||
const arr: Array<Element> = [];
|
||||
|
||||
each((elm || document).querySelectorAll(selector), (e: Element) => {
|
||||
@@ -38,7 +38,7 @@ const is = (elm: InputElementType, selector: string): boolean => {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const fn = elmPrototype.matches || elmPrototype.msMatchesSelector;
|
||||
return fn.call(elm, selector);
|
||||
return fn && fn.call(elm, selector);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -101,8 +101,8 @@ const closest = (elm: InputElementType, selector: string): OutputElementType =>
|
||||
* @param deepBoundarySelector The deep boundary selector.
|
||||
*/
|
||||
const liesBetween = (elm: InputElementType, highBoundarySelector: string, deepBoundarySelector: string): boolean => {
|
||||
const closestHighBoundaryElm = closest(elm, highBoundarySelector);
|
||||
const closestDeepBoundaryElm = findFirst(deepBoundarySelector, closestHighBoundaryElm);
|
||||
const closestHighBoundaryElm = elm && closest(elm, highBoundarySelector);
|
||||
const closestDeepBoundaryElm = elm && findFirst(deepBoundarySelector, closestHighBoundaryElm);
|
||||
|
||||
return closestHighBoundaryElm && closestDeepBoundaryElm
|
||||
? closestHighBoundaryElm === elm ||
|
||||
|
||||
+295
-145
@@ -1,185 +1,335 @@
|
||||
import 'overlayscrollbars.scss';
|
||||
import './index.scss';
|
||||
import should from 'should';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||
import { setTestResult } from '@/testing-browser/TestResult';
|
||||
import { hasDimensions, offsetSize, WH, style } from 'support';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { appendChildren, createDiv, removeElements, children, isArray, isNumber, liesBetween, hasClass } from 'support';
|
||||
|
||||
import { createSizeObserver } from 'observers/sizeObserver';
|
||||
import { createDOMObserver } from 'observers/domObserver';
|
||||
|
||||
let sizeIterations = 0;
|
||||
let directionIterations = 0;
|
||||
const contentBox = (elm: HTMLElement | null): WH<number> => {
|
||||
if (elm) {
|
||||
const computedStyle = window.getComputedStyle(elm);
|
||||
return {
|
||||
w: elm.clientWidth - (parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
|
||||
h: elm.clientHeight - (parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
|
||||
};
|
||||
}
|
||||
interface DOMObserverResult {
|
||||
changedTargetAttrs: string[];
|
||||
styleChanged: boolean;
|
||||
contentChanged: boolean;
|
||||
}
|
||||
interface SeparateChangeThrough {
|
||||
added?: DOMObserverResult[];
|
||||
removed?: DOMObserverResult[];
|
||||
}
|
||||
|
||||
return { w: 0, h: 0 };
|
||||
};
|
||||
const targetChangesCountSlot: HTMLElement | null = document.querySelector('#targetChanges');
|
||||
const contentChangesCountSlot: HTMLElement | null = document.querySelector('#contentChanges');
|
||||
const targetElm: HTMLElement | null = document.querySelector('#target');
|
||||
const contentElmAttrChange: HTMLElement | null = document.querySelector('#target .content-nest');
|
||||
const contentBetweenElmAttrChange: HTMLElement | null = document.querySelector('#content-host .padding-nest-item');
|
||||
const contentHostElmAttrChange: HTMLElement | null = document.querySelector('#content-nest-item-host');
|
||||
|
||||
const targetElmsSlot = document.querySelector('#target .host-nest-item');
|
||||
const targetContentElmsSlot = document.querySelector('#target .content .content-nest');
|
||||
const targetContentBetweenElmsSlot = document.querySelector('#content-host');
|
||||
|
||||
const addRemoveTargetElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetElms');
|
||||
const addRemoveTargetContentElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentElms');
|
||||
const addRemoveTargetContentBetweenElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentBetweenElms');
|
||||
const setTargetAttr: HTMLSelectElement | null = document.querySelector('#setTargetAttr');
|
||||
const setFilteredTargetAttr: HTMLSelectElement | null = document.querySelector('#setFilteredTargetAttr');
|
||||
const setContentAttr: HTMLSelectElement | null = document.querySelector('#setContentAttr');
|
||||
const setFilteredContentAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentAttr');
|
||||
const setContentBetweenAttr: HTMLSelectElement | null = document.querySelector('#setContentBetweenAttr');
|
||||
const setFilteredContentBetweenAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentBetweenAttr');
|
||||
const setContentHostElmAttr: HTMLSelectElement | null = document.querySelector('#setContentHostElmAttr');
|
||||
const setFilteredContentHostElmAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentHostElmAttr');
|
||||
const summaryContent: HTMLElement | null = document.querySelector('#summary-content');
|
||||
const summaryBetween: HTMLElement | null = document.querySelector('#summary-between');
|
||||
|
||||
const targetElm = document.querySelector('#target');
|
||||
const heightSelect: HTMLSelectElement | null = document.querySelector('#height');
|
||||
const widthSelect: HTMLSelectElement | null = document.querySelector('#width');
|
||||
const paddingSelect: HTMLSelectElement | null = document.querySelector('#padding');
|
||||
const borderSelect: HTMLSelectElement | null = document.querySelector('#border');
|
||||
const boxSizingSelect: HTMLSelectElement | null = document.querySelector('#boxSizing');
|
||||
const displaySelect: HTMLSelectElement | null = document.querySelector('#display');
|
||||
const directionSelect: HTMLSelectElement | null = document.querySelector('#direction');
|
||||
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes');
|
||||
|
||||
const selectCallback = generateSelectCallback(targetElm as HTMLElement);
|
||||
const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) => {
|
||||
interface IterateSelect {
|
||||
currSizeIterations: number;
|
||||
currDirectionIterations: number;
|
||||
currOffsetSize: WH<number>;
|
||||
currContentSize: WH<number>;
|
||||
currDir: string;
|
||||
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 changedThrough = (observationLists?: Array<DOMObserverResult[]> | DOMObserverResult[]) => {
|
||||
interface Stat {
|
||||
total: number;
|
||||
lists: Array<[DOMObserverResult[], number]>;
|
||||
}
|
||||
const noObservationLists = observationLists === undefined;
|
||||
let before: Stat;
|
||||
let after: Stat;
|
||||
if (noObservationLists) {
|
||||
observationLists = [];
|
||||
}
|
||||
if (isArray(observationLists) && !isArray(observationLists[0])) {
|
||||
observationLists = [observationLists] as Array<DOMObserverResult[]>;
|
||||
}
|
||||
|
||||
await iterateSelect<IterateSelect>(select, {
|
||||
const getStats = (): Stat => {
|
||||
return {
|
||||
total: getTotalObservations(),
|
||||
lists: (observationLists as Array<DOMObserverResult[]>).map((list) => [list, list.length]),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
before: () => {
|
||||
before = getStats();
|
||||
},
|
||||
after: () => {
|
||||
after = getStats();
|
||||
},
|
||||
compare: (comparisonTableOrNumber: number | Map<DOMObserverResult[], number> = 0) => {
|
||||
let totalDiff = 0;
|
||||
if (isNumber(comparisonTableOrNumber) || noObservationLists) {
|
||||
before.lists.forEach((_, index) => {
|
||||
const [, beforeCount] = before.lists[index];
|
||||
const [, afterCount] = after.lists[index];
|
||||
|
||||
totalDiff += afterCount - beforeCount;
|
||||
should(afterCount).equal(beforeCount + (noObservationLists ? 0 : (comparisonTableOrNumber as number)));
|
||||
});
|
||||
} else {
|
||||
before.lists.forEach((_, index) => {
|
||||
const [list, beforeCount] = before.lists[index];
|
||||
const [, afterCount] = after.lists[index];
|
||||
|
||||
totalDiff += afterCount - beforeCount;
|
||||
should(afterCount).equal(beforeCount + (comparisonTableOrNumber.get(list) || 0));
|
||||
});
|
||||
}
|
||||
should(after.total).equal(before.total + totalDiff);
|
||||
},
|
||||
};
|
||||
};
|
||||
const attrChangeListener = (attrChangeTarget: HTMLElement | null) =>
|
||||
generateSelectCallback(attrChangeTarget, (target, possibleValues, selectedValue) => {
|
||||
const isClass = selectedValue === 'class';
|
||||
|
||||
target.classList.remove('something');
|
||||
possibleValues.forEach((val) => val !== 'class' && target.removeAttribute(val));
|
||||
isClass && target.classList.add('something');
|
||||
!isClass && target.setAttribute(selectedValue, 'something');
|
||||
});
|
||||
const iterateAttrChange = async (
|
||||
select: HTMLSelectElement | null,
|
||||
changeThrough?: DOMObserverResult[],
|
||||
checkChange?: (observation: DOMObserverResult, selected: string) => any
|
||||
) => {
|
||||
const { before, after, compare } = changedThrough(changeThrough);
|
||||
|
||||
await iterateSelect<unknown>(select, {
|
||||
beforeEach() {
|
||||
const currSizeIterations = sizeIterations;
|
||||
const currDirectionIterations = directionIterations;
|
||||
const currOffsetSize = offsetSize(targetElm as HTMLElement);
|
||||
const currContentSize = contentBox(targetElm as HTMLElement);
|
||||
const currDir = style(targetElm as HTMLElement, 'direction');
|
||||
|
||||
return {
|
||||
currSizeIterations,
|
||||
currDirectionIterations,
|
||||
currOffsetSize,
|
||||
currContentSize,
|
||||
currDir,
|
||||
};
|
||||
before();
|
||||
},
|
||||
async check({ currSizeIterations, currDirectionIterations, currOffsetSize, currContentSize, currDir }) {
|
||||
const newOffsetSize = offsetSize(targetElm as HTMLElement);
|
||||
const newContentSize = contentBox(targetElm as HTMLElement);
|
||||
const newDir = style(targetElm as HTMLElement, 'direction');
|
||||
const offsetSizeChanged = currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
|
||||
const contentSizeChanged = currContentSize.w !== newContentSize.w || currContentSize.h !== newContentSize.h;
|
||||
const dirChanged = currDir !== newDir;
|
||||
const dimensions = hasDimensions(targetElm as HTMLElement);
|
||||
const observerElm = targetElm?.firstElementChild as HTMLElement;
|
||||
async check(_, selected) {
|
||||
await waitForOrFailTest(async () => {
|
||||
after();
|
||||
|
||||
// no overflow if not needed
|
||||
if (targetElm && newContentSize.w > 0) {
|
||||
should.ok(observerElm.getBoundingClientRect().right <= targetElm.getBoundingClientRect().right);
|
||||
}
|
||||
if (targetElm && newContentSize.h > 0) {
|
||||
should.ok(observerElm.getBoundingClientRect().bottom <= targetElm.getBoundingClientRect().bottom);
|
||||
}
|
||||
|
||||
if (dimensions && (offsetSizeChanged || contentSizeChanged || dirChanged)) {
|
||||
await waitFor(
|
||||
() => {
|
||||
if (offsetSizeChanged || contentSizeChanged) {
|
||||
should.equal(sizeIterations, currSizeIterations + 1);
|
||||
}
|
||||
if (dirChanged) {
|
||||
should.equal(directionIterations, currDirectionIterations + 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
onTimeout(error): Error {
|
||||
setTestResult(false);
|
||||
return error;
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
if (changeThrough) {
|
||||
compare(1);
|
||||
checkChange && checkChange(getLast(changeThrough), selected);
|
||||
} else {
|
||||
await timeout(250);
|
||||
compare(0);
|
||||
}
|
||||
});
|
||||
},
|
||||
afterEach,
|
||||
});
|
||||
};
|
||||
const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMObserverResult[] | SeparateChangeThrough) => {
|
||||
if (slot) {
|
||||
let addChangeThrough: DOMObserverResult[] | undefined = changeThrough as DOMObserverResult[] | undefined;
|
||||
let removeChangeThrough: DOMObserverResult[] | undefined = changeThrough as DOMObserverResult[] | undefined;
|
||||
if (changeThrough && !isArray(changeThrough)) {
|
||||
addChangeThrough = (changeThrough as SeparateChangeThrough).added;
|
||||
removeChangeThrough = (changeThrough as SeparateChangeThrough).removed;
|
||||
}
|
||||
|
||||
heightSelect?.addEventListener('change', selectCallback);
|
||||
widthSelect?.addEventListener('change', selectCallback);
|
||||
paddingSelect?.addEventListener('change', selectCallback);
|
||||
borderSelect?.addEventListener('change', selectCallback);
|
||||
boxSizingSelect?.addEventListener('change', selectCallback);
|
||||
displaySelect?.addEventListener('change', selectCallback);
|
||||
directionSelect?.addEventListener('change', selectCallback);
|
||||
const addElm = async () => {
|
||||
const { before, after, compare } = changedThrough(addChangeThrough);
|
||||
|
||||
selectCallback(heightSelect);
|
||||
selectCallback(widthSelect);
|
||||
selectCallback(paddingSelect);
|
||||
selectCallback(borderSelect);
|
||||
selectCallback(boxSizingSelect);
|
||||
selectCallback(displaySelect);
|
||||
selectCallback(directionSelect);
|
||||
before();
|
||||
appendChildren(slot, createDiv('addedElm'));
|
||||
await timeout(250);
|
||||
after();
|
||||
|
||||
const iteratePadding = async (afterEach?: () => any) => {
|
||||
await iterate(paddingSelect, afterEach);
|
||||
await waitForOrFailTest(() => {
|
||||
compare(1);
|
||||
});
|
||||
|
||||
if (addChangeThrough) {
|
||||
const { contentChanged, styleChanged, changedTargetAttrs } = getLast(addChangeThrough);
|
||||
await waitForOrFailTest(() => {
|
||||
should(contentChanged).equal(true);
|
||||
should(styleChanged).equal(false);
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const removeElm = async () => {
|
||||
const removeItem = children(slot, '.addedElm')[0];
|
||||
const { before, after, compare } = changedThrough(removeChangeThrough);
|
||||
|
||||
if (removeItem) {
|
||||
before();
|
||||
removeElements(removeItem);
|
||||
await timeout(250);
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
after();
|
||||
compare(1);
|
||||
|
||||
if (removeChangeThrough) {
|
||||
const { changedTargetAttrs, styleChanged, contentChanged } = getLast(removeChangeThrough);
|
||||
should(changedTargetAttrs.length).equal(0);
|
||||
should(styleChanged).equal(false);
|
||||
should(contentChanged).equal(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
await addElm();
|
||||
await addElm();
|
||||
await addElm();
|
||||
|
||||
await removeElm();
|
||||
await removeElm();
|
||||
await removeElm();
|
||||
}
|
||||
};
|
||||
const iterateBorder = async (afterEach?: () => any) => {
|
||||
await iterate(borderSelect, afterEach);
|
||||
const triggerSummaryElemet = async (summaryElm: HTMLElement | null, changeThrough?: DOMObserverResult[]) => {
|
||||
// onyl do if summary is working (IE. exception)
|
||||
if (summaryElm && (summaryElm.nextElementSibling as HTMLElement)?.offsetHeight === 0) {
|
||||
const click = async () => {
|
||||
const { before, after, compare } = changedThrough(changeThrough);
|
||||
|
||||
before();
|
||||
summaryElm?.click();
|
||||
await timeout(250);
|
||||
after();
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
compare(1);
|
||||
});
|
||||
};
|
||||
|
||||
await click();
|
||||
await click();
|
||||
}
|
||||
};
|
||||
const iterateHeight = async (afterEach?: () => any) => {
|
||||
await iterate(heightSelect, afterEach);
|
||||
|
||||
const addRemoveTargetElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetElmsSlot);
|
||||
};
|
||||
const iterateWidth = async (afterEach?: () => any) => {
|
||||
await iterate(widthSelect, afterEach);
|
||||
const addRemoveTargetContentElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetContentElmsSlot, targetElmContentElmObservations);
|
||||
};
|
||||
const iterateBoxSizing = async (afterEach?: () => any) => {
|
||||
await iterate(boxSizingSelect, afterEach);
|
||||
const addRemoveTargetContentBetweenElmsFn = async () => {
|
||||
await addRemoveElementsTest(targetContentBetweenElmsSlot, targetElmContentElmObservations);
|
||||
};
|
||||
const iterateDisplay = async (afterEach?: () => any) => {
|
||||
await iterate(displaySelect, afterEach);
|
||||
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(setFilteredTargetAttr);
|
||||
};
|
||||
const iterateDirection = async (afterEach?: () => any) => {
|
||||
await iterate(directionSelect, afterEach);
|
||||
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(setFilteredContentAttr);
|
||||
};
|
||||
const iterateContentBetweenAttrChange = async () => {
|
||||
await iterateAttrChange(setContentBetweenAttr);
|
||||
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(setFilteredContentHostElmAttr);
|
||||
};
|
||||
const triggerContentSummaryChange = async () => {
|
||||
await triggerSummaryElemet(summaryContent, targetElmContentElmObservations);
|
||||
};
|
||||
const triggerBetweenSummaryChange = async () => {
|
||||
await triggerSummaryElemet(summaryBetween);
|
||||
};
|
||||
|
||||
addRemoveTargetElms?.addEventListener('click', addRemoveTargetElmsFn);
|
||||
addRemoveTargetContentElms?.addEventListener('click', addRemoveTargetContentElmsFn);
|
||||
addRemoveTargetContentBetweenElms?.addEventListener('click', addRemoveTargetContentBetweenElmsFn);
|
||||
setTargetAttr?.addEventListener('change', attrChangeListener(targetElm));
|
||||
setFilteredTargetAttr?.addEventListener('change', attrChangeListener(targetElm));
|
||||
setContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange));
|
||||
setFilteredContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange));
|
||||
setContentBetweenAttr?.addEventListener('change', attrChangeListener(contentBetweenElmAttrChange));
|
||||
setFilteredContentBetweenAttr?.addEventListener('change', attrChangeListener(contentBetweenElmAttrChange));
|
||||
setContentHostElmAttr?.addEventListener('change', attrChangeListener(contentHostElmAttrChange));
|
||||
setFilteredContentHostElmAttr?.addEventListener('change', attrChangeListener(contentHostElmAttrChange));
|
||||
|
||||
createDOMObserver(
|
||||
document.querySelector('#target') as HTMLElement,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => {
|
||||
targetElmObservations.push({ changedTargetAttrs, styleChanged, contentChanged });
|
||||
requestAnimationFrame(() => {
|
||||
if (targetChangesCountSlot) {
|
||||
targetChangesCountSlot.textContent = `${targetElmObservations.length}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
_attributes: ['data-target'],
|
||||
}
|
||||
);
|
||||
createDOMObserver(
|
||||
document.querySelector('#target .content') as HTMLElement,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => {
|
||||
targetElmContentElmObservations.push({ changedTargetAttrs, styleChanged, contentChanged });
|
||||
requestAnimationFrame(() => {
|
||||
if (contentChangesCountSlot) {
|
||||
contentChangesCountSlot.textContent = `${targetElmContentElmObservations.length}`;
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
_observeContent: true,
|
||||
_ignoreContentChange: (mutation) => {
|
||||
const { target, attributeName } = mutation;
|
||||
return attributeName ? !hasClass(target as Element, 'host') && liesBetween(target as Element, '.host', '.content') : false;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const start = async () => {
|
||||
setTestResult(null);
|
||||
|
||||
console.log('init direction changes:', directionIterations);
|
||||
console.log('init size changes:', sizeIterations);
|
||||
should.ok(directionIterations > 0);
|
||||
should.ok(sizeIterations > 0);
|
||||
await addRemoveTargetElmsFn();
|
||||
await addRemoveTargetContentElmsFn();
|
||||
await addRemoveTargetContentBetweenElmsFn();
|
||||
|
||||
targetElm?.removeAttribute('style');
|
||||
await iterateDisplay();
|
||||
await iterateDirection();
|
||||
await iterateBoxSizing(async () => {
|
||||
await iterateHeight(async () => {
|
||||
await iterateWidth(async () => {
|
||||
await iterateBorder(async () => {
|
||||
await iterateDirection();
|
||||
await iteratePadding();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
await iterateTargetAttrChange();
|
||||
await iterateContentAttrChange();
|
||||
await iterateContentBetweenAttrChange();
|
||||
await iterateContentHostElmAttrChange();
|
||||
|
||||
await triggerContentSummaryChange();
|
||||
await triggerBetweenSummaryChange();
|
||||
|
||||
setTestResult(true);
|
||||
};
|
||||
|
||||
startBtn?.addEventListener('click', start);
|
||||
|
||||
createSizeObserver(
|
||||
targetElm as HTMLElement,
|
||||
(directionCache?: any) => {
|
||||
if (directionCache) {
|
||||
directionIterations += 1;
|
||||
} else {
|
||||
sizeIterations += 1;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
if (resizesSlot) {
|
||||
resizesSlot.textContent = (directionIterations + sizeIterations).toString();
|
||||
}
|
||||
});
|
||||
},
|
||||
{ _direction: true, _appear: true }
|
||||
);
|
||||
|
||||
export { start };
|
||||
|
||||
@@ -1,49 +1,144 @@
|
||||
<div id="controls">
|
||||
<label for="height">height</label>
|
||||
<select name="height" id="height">
|
||||
<option value="heightAuto">auto</option>
|
||||
<option value="heightHundred">100%</option>
|
||||
<option value="height200">200px</option>
|
||||
<button id="addRemoveTargetElms">Target Elements</button>
|
||||
<button id="addRemoveTargetContentElms">Content Elements</button>
|
||||
<button id="addRemoveTargetContentBetweenElms">Content Between Elements</button>
|
||||
|
||||
<label for="setTargetAttr">setTargetAttr</label>
|
||||
<select name="setTargetAttr" id="setTargetAttr">
|
||||
<option value="id">id</option>
|
||||
<option value="class">class</option>
|
||||
<option value="style">style</option>
|
||||
<option value="data-target">data-target</option>
|
||||
</select>
|
||||
<label for="width">width</label>
|
||||
<select name="width" id="width">
|
||||
<option value="widthAuto">auto</option>
|
||||
<option value="widthHundred">100%</option>
|
||||
<option value="width200">200px</option>
|
||||
|
||||
<label for="setFilteredTargetAttr">setFilteredTargetAttr</label>
|
||||
<select name="setFilteredTargetAttr" id="setFilteredTargetAttr">
|
||||
<option value="data-something-a">data-something-a</option>
|
||||
<option value="data-something-b">data-something-b</option>
|
||||
<option value="data-something-c">data-something-c</option>
|
||||
</select>
|
||||
<label for="padding">padding</label>
|
||||
<select name="padding" id="padding">
|
||||
<option value="padding0">0</option>
|
||||
<option value="padding10">10px</option>
|
||||
<option value="padding50">50px</option>
|
||||
|
||||
<label for="setContentAttr">setContentAttr</label>
|
||||
<select name="setContentAttr" id="setContentAttr">
|
||||
<option value="id">id</option>
|
||||
<option value="class">class</option>
|
||||
<option value="style">style</option>
|
||||
<option value="data-target">data-target</option>
|
||||
</select>
|
||||
<label for="border">border</label>
|
||||
<select name="border" id="border">
|
||||
<option value="border2">2px</option>
|
||||
<option value="border10">10px</option>
|
||||
<option value="border0">0</option>
|
||||
|
||||
<label for="setFilteredContentAttr">setFilteredContentAttr</label>
|
||||
<select name="setFilteredContentAttr" id="setFilteredContentAttr">
|
||||
<option value="data-something-a">data-something-a</option>
|
||||
<option value="data-something-b">data-something-b</option>
|
||||
<option value="data-something-c">data-something-c</option>
|
||||
</select>
|
||||
<label for="boxSizing">boxSizing</label>
|
||||
<select name="boxSizing" id="boxSizing">
|
||||
<option value="boxSizingBorderBox">border-box</option>
|
||||
<option value="boxSizingContentBox">content-box</option>
|
||||
|
||||
<label for="setContentBetweenAttr">setContentBetweenAttr</label>
|
||||
<select name="setContentBetweenAttr" id="setContentBetweenAttr">
|
||||
<option value="id">id</option>
|
||||
<option value="class">class</option>
|
||||
<option value="style">style</option>
|
||||
<option value="data-target">data-target</option>
|
||||
</select>
|
||||
<label for="display">display</label>
|
||||
<select name="display" id="display">
|
||||
<option value="displayBlock">block</option>
|
||||
<option value="displayNone">none</option>
|
||||
|
||||
<label for="setFilteredContentBetweenAttr">setFilteredContentBetweenAttr</label>
|
||||
<select name="setFilteredContentBetweenAttr" id="setFilteredContentBetweenAttr">
|
||||
<option value="data-something-a">data-something-a</option>
|
||||
<option value="data-something-b">data-something-b</option>
|
||||
<option value="data-something-c">data-something-c</option>
|
||||
</select>
|
||||
<label for="direction">direction</label>
|
||||
<select name="direction" id="direction">
|
||||
<option value="directionLTR">ltr</option>
|
||||
<option value="directionRTL">rtl</option>
|
||||
|
||||
<label for="setContentHostElmAttr">setContentHostElmAttr</label>
|
||||
<select name="setContentHostElmAttr" id="setContentHostElmAttr">
|
||||
<option value="id">id</option>
|
||||
<option value="class">class</option>
|
||||
<option value="style">style</option>
|
||||
<option value="data-target">data-target</option>
|
||||
</select>
|
||||
|
||||
<label for="setFilteredContentHostElmAttr">setFilteredContentHostElmAttr</label>
|
||||
<select name="setFilteredContentHostElmAttr" id="setFilteredContentHostElmAttr">
|
||||
<option value="data-something-a">data-something-a</option>
|
||||
<option value="data-something-b">data-something-b</option>
|
||||
<option value="data-something-c">data-something-c</option>
|
||||
</select>
|
||||
|
||||
<button id="start">start</button>
|
||||
<span>Detected resizes: <span id="resizes">0</span></span>
|
||||
<span>Detected target changes: <span id="targetChanges">0</span></span>
|
||||
<span>Detected content changes: <span id="contentChanges">0</span></span>
|
||||
</div>
|
||||
<div id="stage">
|
||||
<div>
|
||||
<div id="target"></div>
|
||||
<div id="target" class="host">
|
||||
<div class="host-nest">
|
||||
<div class="host-nest-item"></div>
|
||||
</div>
|
||||
<div class="padding">
|
||||
<div class="padding-nest">
|
||||
<div class="padding-nest-item"></div>
|
||||
</div>
|
||||
<div class="viewport">
|
||||
<div class="viewport-nest">
|
||||
<div class="viewport-nest-item"></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content-nest">
|
||||
<div class="content-nest-item">
|
||||
<div id="content-nest-item-host" class="host">
|
||||
<div class="host-nest">
|
||||
<div class="host-nest-item"></div>
|
||||
</div>
|
||||
<div class="padding">
|
||||
<div class="padding-nest">
|
||||
<div class="padding-nest-item"></div>
|
||||
</div>
|
||||
<div class="viewport">
|
||||
<div class="viewport-nest">
|
||||
<div class="viewport-nest-item"></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content-nest">
|
||||
<div class="content-nest-item">
|
||||
<details>
|
||||
<summary id="summary-content">Triggers DOM Change</summary>
|
||||
<p>DOM Content Change should be triggered</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content-host" class="host">
|
||||
<div class="host-nest">
|
||||
<div class="host-nest-item">
|
||||
<details>
|
||||
<summary id="summary-between">Won't trigger DOM Change</summary>
|
||||
<p>DOM Content Change shouldn't be triggered</p>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
<div class="padding">
|
||||
<div class="padding-nest">
|
||||
<div class="padding-nest-item"></div>
|
||||
</div>
|
||||
<div class="viewport">
|
||||
<div class="viewport-nest">
|
||||
<div class="viewport-nest-item"></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content-nest">
|
||||
<div class="content-nest-item"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,73 +27,51 @@ body {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#target {
|
||||
overflow: hidden;
|
||||
resize: both;
|
||||
position: relative;
|
||||
// prevent container from reaching 0x0 dimensions for testing purposes
|
||||
min-width: 50px;
|
||||
min-height: 50px;
|
||||
.addedElm {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
.padding0 {
|
||||
padding: 0;
|
||||
}
|
||||
.padding10 {
|
||||
padding: 10px;
|
||||
}
|
||||
.padding50 {
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.border2 {
|
||||
border: 2px solid red;
|
||||
}
|
||||
.border10 {
|
||||
border: 10px solid red;
|
||||
}
|
||||
.border0 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.heightAuto {
|
||||
height: auto;
|
||||
}
|
||||
.height200 {
|
||||
height: 200px;
|
||||
}
|
||||
.heightHundred {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.widthAuto {
|
||||
width: auto;
|
||||
float: left;
|
||||
}
|
||||
.width200 {
|
||||
width: 200px;
|
||||
}
|
||||
.widthHundred {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boxSizingBorderBox {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.boxSizingContentBox {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.displayNone {
|
||||
display: none;
|
||||
}
|
||||
.displayBlock {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.directionltr {
|
||||
direction: ltr;
|
||||
}
|
||||
.directionRTL {
|
||||
direction: rtl;
|
||||
.host {
|
||||
color: black;
|
||||
border: 1px solid red;
|
||||
background: red;
|
||||
& > .host-nest {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
& > .host-nest-item {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
& > .padding {
|
||||
border: 1px solid green;
|
||||
background: green;
|
||||
& > .padding-nest {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
& > .padding-nest-item {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
& > .viewport {
|
||||
border: 1px solid blue;
|
||||
background: blue;
|
||||
& > .viewport-nest {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
& > .viewport-nest-item {
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
& > .content {
|
||||
border: 1px solid black;
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-19
@@ -1,9 +1,8 @@
|
||||
import 'overlayscrollbars.scss';
|
||||
import './index.scss';
|
||||
import should from 'should';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||
import { setTestResult } from '@/testing-browser/TestResult';
|
||||
import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { hasDimensions, offsetSize, WH, style } from 'support';
|
||||
|
||||
import { createSizeObserver } from 'observers/sizeObserver';
|
||||
@@ -33,7 +32,7 @@ const directionSelect: HTMLSelectElement | null = document.querySelector('#direc
|
||||
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes');
|
||||
|
||||
const selectCallback = generateSelectCallback(targetElm as HTMLElement);
|
||||
const selectCallback = generateClassChangeSelectCallback(targetElm as HTMLElement);
|
||||
const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) => {
|
||||
interface IterateSelect {
|
||||
currSizeIterations: number;
|
||||
@@ -78,22 +77,14 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
}
|
||||
|
||||
if (dimensions && (offsetSizeChanged || contentSizeChanged || dirChanged)) {
|
||||
await waitFor(
|
||||
() => {
|
||||
if (offsetSizeChanged || contentSizeChanged) {
|
||||
should.equal(sizeIterations, currSizeIterations + 1);
|
||||
}
|
||||
if (dirChanged) {
|
||||
should.equal(directionIterations, currDirectionIterations + 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
onTimeout(error): Error {
|
||||
setTestResult(false);
|
||||
return error;
|
||||
},
|
||||
await waitForOrFailTest(() => {
|
||||
if (offsetSizeChanged || contentSizeChanged) {
|
||||
should.equal(sizeIterations, currSizeIterations + 1);
|
||||
}
|
||||
);
|
||||
if (dirChanged) {
|
||||
should.equal(directionIterations, currDirectionIterations + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
afterEach,
|
||||
|
||||
+10
-18
@@ -1,21 +1,13 @@
|
||||
import 'overlayscrollbars.scss';
|
||||
import './index.scss';
|
||||
import should from 'should';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import { generateSelectCallback, iterateSelect, selectOption } from '@/testing-browser/Select';
|
||||
import { generateClassChangeSelectCallback, iterateSelect, selectOption } from '@/testing-browser/Select';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { setTestResult } from '@/testing-browser/TestResult';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { offsetSize } from 'support';
|
||||
|
||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||
|
||||
const waitForOptions = {
|
||||
onTimeout(error: Error): Error {
|
||||
setTestResult(false);
|
||||
return error;
|
||||
},
|
||||
};
|
||||
|
||||
let heightIntrinsic: boolean | undefined;
|
||||
let heightIterations = 0;
|
||||
const envElm = document.querySelector('#env');
|
||||
@@ -27,8 +19,8 @@ const displaySelect: HTMLSelectElement | null = document.querySelector('#display
|
||||
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const changesSlot: HTMLButtonElement | null = document.querySelector('#changes');
|
||||
|
||||
const envElmSelectCallback = generateSelectCallback(envElm as HTMLElement);
|
||||
const targetElmSelectCallback = generateSelectCallback(targetElm as HTMLElement);
|
||||
const envElmSelectCallback = generateClassChangeSelectCallback(envElm as HTMLElement);
|
||||
const targetElmSelectCallback = generateClassChangeSelectCallback(targetElm as HTMLElement);
|
||||
|
||||
envHeightSelect?.addEventListener('change', envElmSelectCallback);
|
||||
targetHeightSelect?.addEventListener('change', targetElmSelectCallback);
|
||||
@@ -57,11 +49,11 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
const newHeightIntrinsic = offsetSize(checkElm as HTMLElement).h === 0;
|
||||
const trinsicHeightChanged = newHeightIntrinsic !== currHeightIntrinsic;
|
||||
|
||||
await waitFor(() => {
|
||||
await waitForOrFailTest(() => {
|
||||
if (trinsicHeightChanged) {
|
||||
should.equal(heightIterations, currHeightIterations + 1);
|
||||
}
|
||||
}, waitForOptions);
|
||||
});
|
||||
},
|
||||
afterEach,
|
||||
});
|
||||
@@ -85,9 +77,9 @@ const changeWhileHidden = async () => {
|
||||
selectOption(envHeightSelect as HTMLSelectElement, 'envHeightHundred');
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitFor(() => {
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, false);
|
||||
}, waitForOptions);
|
||||
});
|
||||
};
|
||||
|
||||
const hundredToAuto = async () => {
|
||||
@@ -99,9 +91,9 @@ const changeWhileHidden = async () => {
|
||||
selectOption(envHeightSelect as HTMLSelectElement, 'envHeightAuto');
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitFor(() => {
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, true);
|
||||
}, waitForOptions);
|
||||
});
|
||||
};
|
||||
|
||||
await autoToHundred();
|
||||
|
||||
@@ -9,19 +9,27 @@ const noop = <T>(): T => {
|
||||
|
||||
const getSelectOptions = (selectElement: HTMLSelectElement) => Array.from(selectElement.options).map((option) => option.value);
|
||||
|
||||
export const generateSelectCallback = (targetElm: HTMLElement | null) => (event: Event | HTMLSelectElement | null) => {
|
||||
export const generateSelectCallback = (
|
||||
targetElm: HTMLElement | null,
|
||||
callback: (targetAffectedElm: HTMLElement, possibleValues: string[], selectedValue: string) => any
|
||||
) => (event: Event | HTMLSelectElement | null) => {
|
||||
const target: HTMLSelectElement | null = isEvent(event) ? (event.target as HTMLSelectElement) : event;
|
||||
if (target) {
|
||||
const selectedOption = target.value;
|
||||
const selectOptions = getSelectOptions(target);
|
||||
|
||||
if (targetElm) {
|
||||
selectOptions.forEach((clazz) => targetElm.classList.remove(clazz));
|
||||
targetElm.classList.add(selectedOption);
|
||||
callback(targetElm, selectOptions, selectedOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const generateClassChangeSelectCallback = (targetElm: HTMLElement | null) =>
|
||||
generateSelectCallback(targetElm, (targetAffectedElm, possibleValues, selectedValue) => {
|
||||
possibleValues.forEach((clazz) => targetAffectedElm.classList.remove(clazz));
|
||||
targetAffectedElm.classList.add(selectedValue);
|
||||
});
|
||||
|
||||
export const selectOption = (select: HTMLSelectElement | null, selectedOption: string | number): boolean => {
|
||||
if (!select) {
|
||||
return false;
|
||||
@@ -56,7 +64,7 @@ export const iterateSelect = async <T>(
|
||||
select: HTMLSelectElement | null,
|
||||
options?: {
|
||||
beforeEach?: () => T | Promise<T>;
|
||||
check?: (input: T) => void | Promise<void>;
|
||||
check?: (input: T, selectedOptions: string) => void | Promise<void>;
|
||||
afterEach?: () => void | Promise<void>;
|
||||
}
|
||||
) => {
|
||||
@@ -71,7 +79,7 @@ export const iterateSelect = async <T>(
|
||||
const beforeEachObj: T = await beforeEach();
|
||||
if (selectOption(select, option)) {
|
||||
// eslint-disable-next-line
|
||||
await check(beforeEachObj);
|
||||
await check(beforeEachObj, option);
|
||||
// eslint-disable-next-line
|
||||
await afterEach();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { waitFor, waitForOptions } from '@testing-library/dom';
|
||||
|
||||
const getTestResultElm = () => document.getElementById('testResult');
|
||||
|
||||
export const setTestResult = (result: boolean | null) => {
|
||||
@@ -15,3 +17,12 @@ export const testPassed = (): boolean => {
|
||||
const elm = getTestResultElm();
|
||||
return elm ? elm.getAttribute('class') === 'passed' : false;
|
||||
};
|
||||
|
||||
export const waitForOrFailTest = <T>(callback: () => T | Promise<T>, options?: waitForOptions) =>
|
||||
waitFor(callback, {
|
||||
...options,
|
||||
onTimeout(error: Error): Error {
|
||||
setTestResult(false);
|
||||
return error;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user