improve tests and prettier

This commit is contained in:
Rene
2022-06-14 16:30:06 +02:00
parent a40a8802ec
commit 3084e25385
12 changed files with 467 additions and 408 deletions
+22 -7
View File
@@ -29,7 +29,9 @@ import { OSTargetElement } from 'typings';
type StructureInitializationElementFn<T> = ((target: OSTargetElement) => HTMLElement | T) | T;
type ScrollbarsInitializationElementFn<T> = ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => HTMLElement | T) | T;
type ScrollbarsInitializationElementFn<T> =
| ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => HTMLElement | T)
| T;
/**
* A Static element is an element which MUST be generated.
@@ -44,7 +46,9 @@ export type StructureInitializationStaticElement = StructureInitializationElemen
* If boolean (or the returned result is boolean), the generation of the element is forced (or not).
* If the function returns and element, the element returned by the function acts as the generated element.
*/
export type StructureInitializationDynamicElement = StructureInitializationElementFn<boolean | null>;
export type StructureInitializationDynamicElement = StructureInitializationElementFn<
boolean | null
>;
export interface StructureInitializationStrategy {
_host: StructureInitializationStaticElement;
@@ -57,7 +61,9 @@ export interface ScrollbarsInitializationStrategy {
_scrollbarsSlot: ScrollbarsInitializationElementFn<null | undefined>;
}
export interface InitializationStrategy extends StructureInitializationStrategy, ScrollbarsInitializationStrategy {}
export interface InitializationStrategy
extends StructureInitializationStrategy,
ScrollbarsInitializationStrategy {}
export type OnEnvironmentChanged = (env: Environment) => void;
export interface Environment {
@@ -103,13 +109,17 @@ const getNativeScrollbarStyling = (testElm: HTMLElement): boolean => {
try {
result =
style(testElm, cssProperty('scrollbar-width')) === 'none' ||
window.getComputedStyle(testElm, '::-webkit-scrollbar').getPropertyValue('display') === 'none';
window.getComputedStyle(testElm, '::-webkit-scrollbar').getPropertyValue('display') ===
'none';
} catch (ex) {}
return result;
};
const getRtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: boolean; n: boolean } => {
const getRtlScrollBehavior = (
parentElm: HTMLElement,
childElm: HTMLElement
): { i: boolean; n: boolean } => {
const strHidden = 'hidden';
style(parentElm, { overflowX: strHidden, overflowY: strHidden, direction: 'rtl' });
scrollLeft(parentElm, 0);
@@ -161,7 +171,9 @@ const getWindowDPR = (): number => {
};
// init function decides for all values
const getDefaultInitializationStrategy = (nativeScrollbarStyling: boolean): InitializationStrategy => ({
const getDefaultInitializationStrategy = (
nativeScrollbarStyling: boolean
): InitializationStrategy => ({
_host: null,
_viewport: null,
_padding: null,
@@ -243,7 +255,10 @@ const createEnvironment = (): Environment => {
const isZoom = deltaIsBigger && difference && dprChanged;
if (isZoom) {
const newScrollbarSize = (environmentInstance._nativeScrollbarSize = getNativeScrollbarSize(body, envElm));
const newScrollbarSize = (environmentInstance._nativeScrollbarSize = getNativeScrollbarSize(
body,
envElm
));
removeElements(envElm);
if (scrollbarSize.x !== newScrollbarSize.x || scrollbarSize.y !== newScrollbarSize.y) {
+1 -1
View File
@@ -34,7 +34,7 @@ export const createCache = <Value, Ctx = undefined>(
update: UpdateCachePropFunction<Value, Ctx>,
options: CacheOptions<Value>
): Cache<Value, Ctx> => {
const { _initialValue, _equal, _alwaysUpdateValues } = options || {};
const { _initialValue, _equal, _alwaysUpdateValues } = options;
let _value: Value = _initialValue;
let _previous: Value | undefined;
@@ -8,7 +8,16 @@ const getDummyStyle = (): CSSStyleDeclaration => createDiv().style;
// https://developer.mozilla.org/en-US/docs/Glossary/Vendor_Prefix
export const cssPrefixes: ReadonlyArray<string> = ['-webkit-', '-moz-', '-o-', '-ms-'];
export const jsPrefixes: ReadonlyArray<string> = ['WebKit', 'Moz', 'O', 'MS', 'webkit', 'moz', 'o', 'ms'];
export const jsPrefixes: ReadonlyArray<string> = [
'WebKit',
'Moz',
'O',
'MS',
'webkit',
'moz',
'o',
'ms',
];
export const jsCache: { [key: string]: any } = {};
export const cssCache: { [key: string]: string } = {};
@@ -35,9 +44,14 @@ export const cssProperty = (name: string): string => {
prefixWithoutDashes + uppercasedName, // webkitTransition
firstLetterToUpper(prefixWithoutDashes) + uppercasedName, // WebkitTransition
];
return !(result = resultPossibilities.find((resultPossibility: string) => elmStyle[resultPossibility] !== undefined));
// eslint-disable-next-line no-return-assign
return !(result = resultPossibilities.find(
(resultPossibility: string) => elmStyle[resultPossibility] !== undefined
));
});
// eslint-disable-next-line no-return-assign
return (cssCache[name] = result || '');
};
@@ -72,6 +86,7 @@ export const cssPropertyValue = (property: string, values: string, suffix?: stri
return !result;
});
// eslint-disable-next-line no-return-assign
return (cssCache[name] = result || '');
};
@@ -26,7 +26,10 @@ export function each<T>(
arrayLikeObject: ArrayLike<T> | null | undefined,
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 | unknown): 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 | unknown
@@ -53,15 +56,22 @@ export function each<T>(
* @param item The item.
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
*/
export const indexOf = <T = any>(arr: Array<T>, item: T, fromIndex?: number): number => arr.indexOf(item, fromIndex);
export const indexOf = <T = any>(arr: Array<T>, item: T, fromIndex?: number): number =>
arr.indexOf(item, fromIndex);
/**
* Pushesh all given items into the given array and returns it.
* @param array The array the items shall be pushed into.
* @param items The items which shall be pushed into the array.
*/
export const push = <T>(array: Array<T>, items: T | ArrayLike<T>, arrayIsSingleItem?: boolean): Array<T> => {
!arrayIsSingleItem && !isString(items) && isArrayLike(items) ? Array.prototype.push.apply(array, items as Array<T>) : array.push(items as T);
export const push = <T>(
array: Array<T>,
items: T | ArrayLike<T>,
arrayIsSingleItem?: boolean
): Array<T> => {
!arrayIsSingleItem && !isString(items) && isArrayLike(items)
? Array.prototype.push.apply(array, items as Array<T>)
: array.push(items as T);
return array;
};
@@ -86,7 +96,8 @@ export const from = <T = any>(arr: ArrayLike<T>) => {
* Check whether the passed array is empty.
* @param array The array which shall be checked.
*/
export const isEmptyArray = (array: Array<any> | null | undefined) => array && array.length === 0;
export const isEmptyArray = (array: Array<any> | null | undefined): boolean =>
!!array && array.length === 0;
/**
* Calls all functions in the passed array/set of functions.
@@ -1,7 +1,7 @@
import { isNumber, isFunction } from 'support/utils/types';
import { from } from 'support/utils/array';
import { rAF, cAF } from 'support/compatibility/apis';
const setT = window.setTimeout;
const clearTimeouts = (id: number | undefined) => {
id && window.clearTimeout(id);
id && cAF!(id);
@@ -42,13 +42,14 @@ export const noop = () => {}; // eslint-disable-line
*/
export const debounce = <FunctionToDebounce extends (...args: any) => any>(
functionToDebounce: FunctionToDebounce,
options: DebounceOptions<FunctionToDebounce>
options?: DebounceOptions<FunctionToDebounce>
): Debounced<FunctionToDebounce> => {
let timeoutId: number | undefined;
let maxTimeoutId: number | undefined;
let prevArguments: Parameters<FunctionToDebounce> | null | undefined;
let latestArguments: Parameters<FunctionToDebounce> | null | undefined;
const { _timeout, _maxDelay, _mergeParams } = options;
const { _timeout, _maxDelay, _mergeParams } = options || {};
const setT = window.setTimeout;
const invokeFunctionToDebounce = function (args: IArguments) {
clearTimeouts(timeoutId);
@@ -59,31 +60,35 @@ export const debounce = <FunctionToDebounce extends (...args: any) => any>(
functionToDebounce.apply(this, args);
};
const mergeParms = (curr: Parameters<FunctionToDebounce>): Parameters<FunctionToDebounce> | false | null | undefined =>
const mergeParms = (
curr: Parameters<FunctionToDebounce>
): Parameters<FunctionToDebounce> | false | null | undefined =>
_mergeParams && prevArguments ? _mergeParams(prevArguments, curr) : curr;
const flush = () => {
/* istanbul ignore next */
if (timeoutId) {
invokeFunctionToDebounce(mergeParms(latestArguments!) || latestArguments!);
}
};
const debouncedFn = function () {
const args: Parameters<FunctionToDebounce> = arguments as Parameters<FunctionToDebounce>;
// eslint-disable-next-line prefer-rest-params
const args: Parameters<FunctionToDebounce> = from(arguments) as Parameters<FunctionToDebounce>;
const finalTimeout = isFunction(_timeout) ? _timeout() : _timeout;
const hasTimeout = isNumber(finalTimeout) && finalTimeout >= 0;
if (hasTimeout) {
const finalMaxWait = isFunction(_maxDelay) ? _maxDelay() : _maxDelay;
const hasMaxWait = isNumber(finalMaxWait) && finalMaxWait >= 0;
const setTimeoutFn = finalTimeout! > 0 ? setT : rAF!;
const setTimeoutFn = finalTimeout > 0 ? setT : rAF!;
const mergeParamsResult = mergeParms(args);
const invokedArgs = mergeParamsResult || args;
const boundInvoke = invokeFunctionToDebounce.bind(0, invokedArgs);
if (!mergeParamsResult) {
invokeFunctionToDebounce(prevArguments || args);
}
// if (!mergeParamsResult) {
// invokeFunctionToDebounce(prevArguments || args);
// }
clearTimeouts(timeoutId);
timeoutId = setTimeoutFn(boundInvoke, finalTimeout as number) as number;
@@ -10,11 +10,11 @@ interface GenericLexicon<T extends boolean> {
}
export interface Lexicon<T extends boolean> extends GenericLexicon<T> {
_inverted: Lexicon<T extends true ? false : true>;
// _inverted: Lexicon<T extends true ? false : true>;
}
export const getLexicon = <T extends boolean = false>(horizontal?: T): Lexicon<T> => {
return {
export const getLexicon = <T extends boolean = false>(horizontal?: T): Lexicon<T> =>
({
_widthHeight: horizontal ? 'width' : 'height',
_WidthHeight: horizontal ? 'Width' : 'Height',
_leftTop: horizontal ? 'left' : 'top',
@@ -23,6 +23,5 @@ export const getLexicon = <T extends boolean = false>(horizontal?: T): Lexicon<T
_XY: horizontal ? 'X' : 'Y',
_wh: horizontal ? 'w' : 'h',
_lt: horizontal ? 'l' : 't',
_inverted: getLexicon(!horizontal),
} as Lexicon<T>;
};
// _inverted: getLexicon(!horizontal),
} as Lexicon<T>);
@@ -1,4 +1,4 @@
import { push, each, from, indexOf, runEach } from 'support/utils/array';
import { push, each, from, indexOf, runEach, isEmptyArray } from 'support/utils/array';
describe('array utilities', () => {
describe('push', () => {
@@ -306,4 +306,10 @@ describe('array utilities', () => {
const idx = indexOf([1, 2, 3], 2);
expect(idx).toBe(1);
});
test('isEmptyArray', () => {
expect(isEmptyArray([])).toBe(true);
expect(isEmptyArray([1, 2, 3])).toBe(false);
expect(isEmptyArray(null)).toBe(false);
});
});
@@ -0,0 +1,321 @@
import { noop, debounce } from 'support/utils/function';
import { rAF } from 'support/compatibility/apis';
jest.mock('support/compatibility/apis', () => {
const originalModule = jest.requireActual('support/compatibility/apis');
return {
...originalModule,
rAF: jest.fn().mockImplementation((...args) => originalModule.rAF(...args)),
};
});
const mockSetTimeout = () => {
const original = window.setTimeout;
// @ts-ignore
const setT = (window.setTimeout = jest.fn((...args) => original(...args)));
return [
setT,
() => {
window.setTimeout = original;
},
];
};
// eslint-disable-next-line no-return-await
const timeout = async (timeout = 100) => await new Promise((r) => setTimeout(r, timeout));
describe('function', () => {
test('noop', () => {
expect(typeof noop).toBe('function');
expect(noop()).toBe(undefined);
});
describe('debounce', () => {
describe('timeout', () => {
test('without timeout', () => {
let i = 0;
const [setT, unmockSetTimeout] = mockSetTimeout();
const debouncedFn = debounce(() => {
i += 1;
});
expect(rAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
debouncedFn();
expect(rAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
expect(i).toBe(1);
unmockSetTimeout();
});
test('with timeout 0', async () => {
let i = 0;
const [setT, unmockSetTimeout] = mockSetTimeout();
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 0 }
);
expect(rAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
debouncedFn();
expect(rAF).toHaveBeenCalledTimes(1);
expect(setT).not.toHaveBeenCalled();
expect(i).toBe(0);
await timeout();
expect(i).toBe(1);
unmockSetTimeout();
});
test('with timeout > 0', async () => {
let i = 0;
const [setT, unmockSetTimeout] = mockSetTimeout();
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 1 }
);
expect(rAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
debouncedFn();
expect(rAF).not.toHaveBeenCalled();
expect(setT).toHaveBeenCalledTimes(1);
expect(i).toBe(0);
await timeout();
expect(i).toBe(1);
unmockSetTimeout();
});
test('with timeout > 0 and multiple calls', async () => {
let i = 0;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 200 }
);
debouncedFn();
await timeout();
expect(i).toBe(0);
debouncedFn();
await timeout();
expect(i).toBe(0);
debouncedFn();
await timeout();
expect(i).toBe(0);
debouncedFn();
await timeout();
expect(i).toBe(0);
debouncedFn();
await timeout(300);
expect(i).toBe(1);
});
test('with timeout function', async () => {
let i = 0;
let timeoutMs = 200;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: () => timeoutMs }
);
debouncedFn();
await timeout();
expect(i).toBe(0);
timeoutMs = 1000;
debouncedFn();
await timeout(500);
expect(i).toBe(0);
await timeout(500);
expect(i).toBe(1);
});
});
describe('maxDelay', () => {
test('without maxDelay', async () => {
let i = 0;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 100 }
);
debouncedFn();
expect(i).toBe(0);
await timeout(150);
expect(i).toBe(1);
});
test('with maxDelay and longer timeout', async () => {
let i = 0;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 10000, _maxDelay: 100 }
);
debouncedFn();
expect(i).toBe(0);
await timeout(150);
expect(i).toBe(1);
});
test('with maxDelay and shorter timeout with multiple calls', async () => {
let i = 0;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 200, _maxDelay: 500 }
);
debouncedFn();
await timeout(150);
expect(i).toBe(0);
debouncedFn();
await timeout(150);
debouncedFn();
await timeout(150);
expect(i).toBe(0);
debouncedFn();
await timeout(150);
expect(i).toBe(1);
});
test('with maxDelay function', async () => {
let i = 0;
let maxDelayMs = 300;
const debouncedFn = debounce(
() => {
i += 1;
},
{ _timeout: 400, _maxDelay: () => maxDelayMs }
);
debouncedFn();
expect(i).toBe(0);
await timeout();
debouncedFn();
expect(i).toBe(0);
await timeout();
maxDelayMs = 800; // this delay will be applied in the next cycle, not instantly
debouncedFn();
expect(i).toBe(0);
await timeout();
debouncedFn();
expect(i).toBe(1); // max delay 300 invoked here
await timeout(300);
debouncedFn();
expect(i).toBe(1);
await timeout(300);
debouncedFn();
expect(i).toBe(1);
await timeout(300);
debouncedFn();
expect(i).toBe(2); // max delay 800 invoked here
});
});
describe('mergeParams', () => {
test('with correct mergeParams function', async () => {
let i = 0;
const _mergeParams = jest.fn((prev: [number, number], curr: [number, number]) => {
const [prevA, prevB] = prev;
const [currA, currB] = curr;
return [prevA + currA, prevB + currB] as [number, number];
});
const debouncedFn = debounce(
(a: number, b: number) => {
i += a * b;
},
{ _timeout: 200, _mergeParams }
);
debouncedFn(1, 1);
expect(i).toBe(0);
expect(_mergeParams).not.toHaveBeenCalled();
await timeout();
debouncedFn(4, 4);
expect(i).toBe(0);
expect(_mergeParams).toHaveBeenLastCalledWith([1, 1], [4, 4]);
await timeout();
debouncedFn(10, 10);
expect(i).toBe(0);
expect(_mergeParams).toHaveBeenLastCalledWith([5, 5], [10, 10]);
await timeout(250);
expect(i).toBe(15 * 15);
});
test('without correct mergeParams function', async () => {
let i = 0;
const _mergeParams = jest.fn(() => null);
const debouncedFn = debounce(
(a, b) => {
i += a * b;
},
{ _timeout: 200, _mergeParams }
);
debouncedFn(1, 1);
expect(i).toBe(0);
expect(_mergeParams).not.toHaveBeenCalled();
await timeout();
debouncedFn(2, 2);
expect(i).toBe(0);
expect(_mergeParams).toHaveBeenLastCalledWith([1, 1], [2, 2]);
await timeout();
debouncedFn(3, 3);
expect(i).toBe(0);
expect(_mergeParams).toHaveBeenLastCalledWith([2, 2], [3, 3]);
await timeout(250);
expect(i).toBe(3 * 3);
});
});
});
});
@@ -0,0 +1,27 @@
import { getLexicon } from 'support/utils/lexicon';
describe('getLexicon', () => {
test('Get vertical Lexicon', () => {
const lexicon = getLexicon();
expect(lexicon._widthHeight).toBe('height');
expect(lexicon._WidthHeight).toBe('Height');
expect(lexicon._leftTop).toBe('top');
expect(lexicon._LeftTop).toBe('Top');
expect(lexicon._xy).toBe('y');
expect(lexicon._XY).toBe('Y');
expect(lexicon._wh).toBe('h');
expect(lexicon._lt).toBe('t');
});
test('Get horizontal Lexicon', () => {
const lexicon = getLexicon(true);
expect(lexicon._widthHeight).toBe('width');
expect(lexicon._WidthHeight).toBe('Width');
expect(lexicon._leftTop).toBe('left');
expect(lexicon._LeftTop).toBe('Left');
expect(lexicon._xy).toBe('x');
expect(lexicon._XY).toBe('X');
expect(lexicon._wh).toBe('w');
expect(lexicon._lt).toBe('l');
});
});
@@ -154,8 +154,8 @@ const targetDomObserver = createDOMObserver(
const createContentDomOserver = (
eventContentChange: Array<[string?, string?] | null | undefined>
) => {
return createDOMObserver(
) =>
createDOMObserver(
trargetContentElm!,
true,
(contentChangedTroughEvent: boolean) => {
@@ -188,7 +188,7 @@ const createContentDomOserver = (
? liesBetween(target as Element, hostSelector, '.content')
: false;
},
_ignoreNestedTargetChange: (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);
@@ -204,7 +204,6 @@ const createContentDomOserver = (
},
}
);
};
let contentDomObserver = createContentDomOserver(contentChange);
@@ -229,12 +228,10 @@ const changedThrough = <ChangeThrough extends DOMContentObserverResult | DOMTarg
observationLists = [observationLists] as Array<ChangeThrough[]>;
}
const getStats = (): Stat => {
return {
total: getTotalObservations(),
lists: (observationLists as Array<ChangeThrough[]>).map((list) => [list, list.length]),
};
};
const getStats = (): Stat => ({
total: getTotalObservations(),
lists: (observationLists as Array<ChangeThrough[]>).map((list) => [list, list.length]),
});
return {
before: () => {