add initialization and environment unit tests

This commit is contained in:
Rene Haas
2022-08-23 16:25:27 +02:00
parent dce2f1b4c6
commit 73419d25e0
10 changed files with 938 additions and 46 deletions
@@ -1,14 +1,19 @@
import { isFunction, isHTMLElement, isNull, isUndefined } from 'support'; import { isFunction, isHTMLElement, isNull, isUndefined } from 'support';
import { getEnvironment } from 'environment'; import { getEnvironment } from 'environment';
import { DeepPartial } from 'typings'; import type { DeepPartial } from 'typings';
import { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
type StaticInitialization = HTMLElement | false | null; type StaticInitialization = HTMLElement | false | null;
type DynamicInitialization = HTMLElement | boolean | null; type DynamicInitialization = HTMLElement | boolean | null;
type FallbackStaticInitializtationElement<Args extends any[]> = Extract<
type FallbackInitializtationElement< StaticInitializationElement<Args>,
InitElm extends StaticInitializationElement<any> | DynamicInitializationElement<any> (...args: Args) => any
> = Extract<InitElm, (...args: any[]) => any> extends (...args: infer P) => any > extends (...args: infer P) => any
? (...args: P) => HTMLElement
: never;
type FallbackDynamicInitializtationElement<Args extends any[]> = Extract<
DynamicInitializationElement<Args>,
(...args: Args) => any
> extends (...args: infer P) => any
? (...args: P) => HTMLElement ? (...args: P) => HTMLElement
: never; : never;
@@ -59,30 +64,30 @@ export type InitializationTarget = InitializationTargetElement | InitializationT
const resolveInitialization = <T>(value: any, args: any): T => const resolveInitialization = <T>(value: any, args: any): T =>
isFunction(value) ? value.apply(0, args) : value; isFunction(value) ? value.apply(0, args) : value;
export const staticInitializationElement = <T extends StaticInitializationElement<any>>( export const staticInitializationElement = <Args extends any[]>(
args: Parameters<Extract<T, (...initializationFnArgs: any[]) => any>>, args: Args,
fallbackStaticInitializationElement: FallbackInitializtationElement<T>, fallbackStaticInitializationElement: FallbackStaticInitializtationElement<Args>,
defaultStaticInitializationElementStrategy: T, defaultStaticInitializationElement: StaticInitializationElement<Args>,
staticInitializationElementValue?: T staticInitializationElementValue?: StaticInitializationElement<Args>
): HTMLElement => { ): HTMLElement => {
const staticInitialization = isUndefined(staticInitializationElementValue) const staticInitialization = isUndefined(staticInitializationElementValue)
? defaultStaticInitializationElementStrategy ? defaultStaticInitializationElement
: staticInitializationElementValue; : staticInitializationElementValue;
const resolvedInitialization = resolveInitialization<StaticInitialization>( const resolvedInitialization = resolveInitialization<StaticInitialization>(
staticInitialization, staticInitialization,
args args
); );
return resolvedInitialization || fallbackStaticInitializationElement(); return resolvedInitialization || fallbackStaticInitializationElement.apply(0, args);
}; };
export const dynamicInitializationElement = <T extends DynamicInitializationElement<any>>( export const dynamicInitializationElement = <Args extends any[]>(
args: Parameters<Extract<T, (...initializationFnArgs: any[]) => any>>, args: Args,
fallbackDynamicInitializationElement: FallbackInitializtationElement<T>, fallbackDynamicInitializationElement: FallbackDynamicInitializtationElement<Args>,
defaultDynamicInitializationElementStrategy: T, defaultDynamicInitializationElement: DynamicInitializationElement<Args>,
dynamicInitializationElementValue?: T dynamicInitializationElementValue?: DynamicInitializationElement<Args>
): HTMLElement | false => { ): HTMLElement | false => {
const dynamicInitialization = isUndefined(dynamicInitializationElementValue) const dynamicInitialization = isUndefined(dynamicInitializationElementValue)
? defaultDynamicInitializationElementStrategy ? defaultDynamicInitializationElement
: dynamicInitializationElementValue; : dynamicInitializationElementValue;
const resolvedInitialization = resolveInitialization<DynamicInitialization>( const resolvedInitialization = resolveInitialization<DynamicInitialization>(
dynamicInitialization, dynamicInitialization,
@@ -92,20 +97,19 @@ export const dynamicInitializationElement = <T extends DynamicInitializationElem
!!resolvedInitialization && !!resolvedInitialization &&
(isHTMLElement(resolvedInitialization) (isHTMLElement(resolvedInitialization)
? resolvedInitialization ? resolvedInitialization
: fallbackDynamicInitializationElement()) : fallbackDynamicInitializationElement.apply(0, args))
); );
}; };
export const cancelInitialization = ( export const cancelInitialization = (
cancelInitializationValue: DeepPartial<Initialization['cancel']> | false | null | undefined, isBody: boolean,
structureSetupElements: StructureSetupElementsObj defaultCancelInitialization: Initialization['cancel'],
cancelInitializationValue?: DeepPartial<Initialization['cancel']> | false | null | undefined
): boolean => { ): boolean => {
const { nativeScrollbarsOverlaid, body } = cancelInitializationValue || {}; const { nativeScrollbarsOverlaid, body } = cancelInitializationValue || {};
const { _isBody } = structureSetupElements; const { _nativeScrollbarsOverlaid, _nativeScrollbarsHiding } = getEnvironment();
const { _getDefaultInitialization, _nativeScrollbarsOverlaid, _nativeScrollbarsHiding } =
getEnvironment();
const { nativeScrollbarsOverlaid: defaultNativeScrollbarsOverlaid, body: defaultbody } = const { nativeScrollbarsOverlaid: defaultNativeScrollbarsOverlaid, body: defaultbody } =
_getDefaultInitialization().cancel; defaultCancelInitialization;
const resolvedNativeScrollbarsOverlaid = const resolvedNativeScrollbarsOverlaid =
nativeScrollbarsOverlaid ?? defaultNativeScrollbarsOverlaid; nativeScrollbarsOverlaid ?? defaultNativeScrollbarsOverlaid;
@@ -115,7 +119,7 @@ export const cancelInitialization = (
(_nativeScrollbarsOverlaid.x || _nativeScrollbarsOverlaid.y) && (_nativeScrollbarsOverlaid.x || _nativeScrollbarsOverlaid.y) &&
resolvedNativeScrollbarsOverlaid; resolvedNativeScrollbarsOverlaid;
const finalDocumentScrollingElement = const finalDocumentScrollingElement =
_isBody && isBody &&
(isNull(resolvedDocumentScrollingElement) (isNull(resolvedDocumentScrollingElement)
? !_nativeScrollbarsHiding ? !_nativeScrollbarsHiding
: resolvedDocumentScrollingElement); : resolvedDocumentScrollingElement);
@@ -115,7 +115,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
options?: DeepPartial<Options>, options?: DeepPartial<Options>,
eventListeners?: InitialEventListeners eventListeners?: InitialEventListeners
) => { ) => {
const { _getDefaultOptions, _addListener: addEnvListener } = getEnvironment(); const {
_getDefaultOptions,
_getDefaultInitialization,
_addListener: addEnvListener,
} = getEnvironment();
const plugins = getPlugins(); const plugins = getPlugins();
const targetIsElement = isHTMLElement(target); const targetIsElement = isHTMLElement(target);
const instanceTarget = targetIsElement ? target : target.target; const instanceTarget = targetIsElement ? target : target.target;
@@ -271,7 +275,13 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
} }
}); });
if (cancelInitialization(!targetIsElement && target.cancel, structureState._elements)) { if (
cancelInitialization(
structureState._elements._isBody,
_getDefaultInitialization().cancel,
!targetIsElement && target.cancel
)
) {
destroy(true); destroy(true);
return instance; return instance;
} }
@@ -28,14 +28,14 @@ import {
getScrollbarHandleOffsetRatio, getScrollbarHandleOffsetRatio,
} from 'setups/scrollbarsSetup/scrollbarsSetup.calculations'; } from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
import type { import type {
Initialization,
InitializationTarget, InitializationTarget,
InitializationTargetElement,
InitializationTargetObject, InitializationTargetObject,
} from 'initialization'; } from 'initialization';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements'; import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { ScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events'; import type { ScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
import type { StyleObject } from 'typings'; import type { StyleObject } from 'typings';
import { StructureSetupState } from 'setups'; import type { StructureSetupState } from 'setups';
export interface ScrollbarStructure { export interface ScrollbarStructure {
_scrollbar: HTMLElement; _scrollbar: HTMLElement;
@@ -84,7 +84,7 @@ export const createScrollbarsSetupElements = (
const { scrollbars: scrollbarsInit } = (_targetIsElm ? {} : target) as InitializationTargetObject; const { scrollbars: scrollbarsInit } = (_targetIsElm ? {} : target) as InitializationTargetObject;
const { slot: initScrollbarsSlot } = scrollbarsInit || {}; const { slot: initScrollbarsSlot } = scrollbarsInit || {};
const evaluatedScrollbarSlot = generalDynamicInitializationElement< const evaluatedScrollbarSlot = generalDynamicInitializationElement<
Initialization['scrollbars']['slot'] [InitializationTargetElement, HTMLElement, HTMLElement]
>([_target, _host, _viewport], () => _host, defaultInitScrollbarsSlot, initScrollbarsSlot); >([_target, _host, _viewport], () => _host, defaultInitScrollbarsSlot, initScrollbarsSlot);
const scrollbarStructureAddRemoveClass = ( const scrollbarStructureAddRemoveClass = (
scrollbarStructures: ScrollbarStructure[], scrollbarStructures: ScrollbarStructure[],
@@ -7,7 +7,7 @@ import {
runEachAndClear, runEachAndClear,
stopPropagation, stopPropagation,
XY, XY,
selfCancelTimeout, selfClearTimeout,
parent, parent,
closest, closest,
rAF, rAF,
@@ -223,7 +223,7 @@ export const createScrollbarsSetupEvents =
isHorizontal isHorizontal
) => { ) => {
const { _scrollbar } = scrollbarStructure; const { _scrollbar } = scrollbarStructure;
const [wheelTimeout, clearScrollTimeout] = selfCancelTimeout(333); const [wheelTimeout, clearScrollTimeout] = selfClearTimeout(333);
const scrollByFn = !!scrollOffsetElm.scrollBy; const scrollByFn = !!scrollOffsetElm.scrollBy;
let wheelScrollBy = true; let wheelScrollBy = true;
@@ -1,4 +1,4 @@
import { on, runEachAndClear, parent, scrollLeft, scrollTop, selfCancelTimeout } from 'support'; import { on, runEachAndClear, parent, scrollLeft, scrollTop, selfClearTimeout } from 'support';
import { createState, createOptionCheck } from 'setups/setups'; import { createState, createOptionCheck } from 'setups/setups';
import { createScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events'; import { createScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
import { import {
@@ -51,11 +51,11 @@ export const createScrollbarsSetup = (
const state = createState({}); const state = createState({});
const [getState] = state; const [getState] = state;
const [requestMouseMoveAnimationFrame, cancelMouseMoveAnimationFrame] = selfCancelTimeout(); const [requestMouseMoveAnimationFrame, cancelMouseMoveAnimationFrame] = selfClearTimeout();
const [requestScrollAnimationFrame, cancelScrollAnimationFrame] = selfCancelTimeout(); const [requestScrollAnimationFrame, cancelScrollAnimationFrame] = selfClearTimeout();
const [scrollTimeout, clearScrollTimeout] = selfCancelTimeout(100); const [scrollTimeout, clearScrollTimeout] = selfClearTimeout(100);
const [auotHideMoveTimeout, clearAutoHideTimeout] = selfCancelTimeout(100); const [auotHideMoveTimeout, clearAutoHideTimeout] = selfClearTimeout(100);
const [auotHideTimeout, clearAutoTimeout] = selfCancelTimeout(() => globalAutoHideDelay); const [auotHideTimeout, clearAutoTimeout] = selfClearTimeout(() => globalAutoHideDelay);
const [elements, appendElements, destroyElements] = createScrollbarsSetupElements( const [elements, appendElements, destroyElements] = createScrollbarsSetupElements(
target, target,
structureSetupState._elements, structureSetupState._elements,
@@ -39,7 +39,6 @@ import {
dynamicInitializationElement as generalDynamicInitializationElement, dynamicInitializationElement as generalDynamicInitializationElement,
} from 'initialization'; } from 'initialization';
import type { import type {
Initialization,
InitializationTarget, InitializationTarget,
InitializationTargetElement, InitializationTargetElement,
InitializationTargetObject, InitializationTargetObject,
@@ -117,10 +116,10 @@ export const createStructureSetupElements = (
const isBody = targetElement === ownerDocument.body; const isBody = targetElement === ownerDocument.body;
const wnd = ownerDocument.defaultView as Window; const wnd = ownerDocument.defaultView as Window;
const staticInitializationElement = generalStaticInitializationElement< const staticInitializationElement = generalStaticInitializationElement<
Initialization['elements']['viewport'] [InitializationTargetElement]
>.bind(0, [targetElement]); >.bind(0, [targetElement]);
const dynamicInitializationElement = generalDynamicInitializationElement< const dynamicInitializationElement = generalDynamicInitializationElement<
Initialization['elements']['content'] [InitializationTargetElement]
>.bind(0, [targetElement]); >.bind(0, [targetElement]);
const viewportElement = staticInitializationElement( const viewportElement = staticInitializationElement(
createNewDiv, createNewDiv,
@@ -30,7 +30,12 @@ export interface Debounced<FunctionToDebounce extends (...args: any) => any> {
export const noop = () => {}; // eslint-disable-line export const noop = () => {}; // eslint-disable-line
export const selfCancelTimeout = (timeout?: number | (() => number)) => { /**
* Creates a timeout and cleartimeout tuple. The timeout function always clears the previously created timeout before it runs.
* @param timeout The timeout in ms. If no timeout (or 0) is passed requestAnimationFrame is used instead of setTimeout.
* @returns A tuple with the timeout function as the first value and the clearTimeout function as the second value.
*/
export const selfClearTimeout = (timeout?: number | (() => number)) => {
let id: number; let id: number;
const setTFn = timeout ? setT : rAF!; const setTFn = timeout ? setT : rAF!;
const clearTFn = timeout ? clearT : cAF!; const clearTFn = timeout ? clearT : cAF!;
@@ -0,0 +1,186 @@
import { DeepPartial } from 'typings';
import { defaultOptions, Options } from 'options';
import { Initialization } from 'initialization';
import { getEnvironment } from 'environment';
import { scrollbarsHidingPlugin, scrollbarsHidingPluginName } from 'plugins';
const defaultInitialization = {
elements: {
host: null,
padding: true,
viewport: expect.any(Function),
content: false,
},
scrollbars: {
slot: true,
},
cancel: {
nativeScrollbarsOverlaid: false,
body: null,
},
};
let getEnv = getEnvironment;
describe('environment', () => {
beforeEach(async () => {
jest.resetModules();
jest.doMock('support', () => {
const originalModule = jest.requireActual('support');
let i = 0;
return {
...originalModule,
offsetSize: jest.fn().mockImplementation(() => {
i += 1;
return { w: 100 + i, h: 100 + i };
}),
clientSize: jest.fn().mockImplementation(() => ({ w: 90, h: 90 })),
};
});
jest.doMock('plugins', () => {
const originalModule = jest.requireActual('plugins');
return {
...originalModule,
getPlugins: jest.fn(() => originalModule.getPlugins()),
};
});
({ getEnvironment: getEnv } = await import('environment'));
});
test('singleton behavior', () => {
const env = getEnv();
expect(env).toBe(getEnv());
});
describe('statics', () => {
test('defaultOptions', () => {
const { _staticDefaultOptions, _getDefaultOptions } = getEnv();
expect(_staticDefaultOptions).not.toBe(defaultOptions);
expect(_staticDefaultOptions).toEqual(defaultOptions);
expect(_staticDefaultOptions).not.toBe(_getDefaultOptions());
expect(_staticDefaultOptions).toEqual(_getDefaultOptions());
});
test('defaultInitialization', () => {
const { _staticDefaultInitialization, _getDefaultInitialization } = getEnv();
expect(_staticDefaultInitialization).not.toBe(defaultInitialization);
expect(_staticDefaultInitialization).toEqual(defaultInitialization);
expect(_staticDefaultInitialization).not.toBe(_getDefaultInitialization());
expect(_staticDefaultInitialization).toEqual(_getDefaultInitialization());
});
});
describe('defaultOptions', () => {
test('get', () => {
const { _getDefaultOptions } = getEnv();
expect(_getDefaultOptions()).not.toBe(defaultOptions);
expect(_getDefaultOptions()).toEqual(defaultOptions);
});
test('set', () => {
const newDefaultOptions: DeepPartial<Options> = {
paddingAbsolute: true,
overflow: {
x: 'hidden',
},
};
const { _getDefaultOptions, _setDefaultOptions } = getEnv();
expect(_getDefaultOptions()).not.toBe(defaultOptions);
expect(_getDefaultOptions()).toEqual(defaultOptions);
_setDefaultOptions(newDefaultOptions);
expect(_getDefaultOptions()).toEqual({
...defaultOptions,
...newDefaultOptions,
overflow: {
...defaultOptions.overflow,
...newDefaultOptions.overflow,
},
});
});
});
describe('defaultInitialization', () => {
test('get', () => {
const { _getDefaultInitialization } = getEnv();
expect(_getDefaultInitialization()).not.toBe(defaultInitialization);
expect(_getDefaultInitialization()).toEqual(defaultInitialization);
});
test('set', () => {
const newDefaultInitialization: DeepPartial<Initialization> = {
elements: {
viewport: false,
padding: false,
},
cancel: {
body: true,
nativeScrollbarsOverlaid: false,
},
};
const { _getDefaultInitialization, _setDefaultInitialization } = getEnv();
expect(_getDefaultInitialization()).not.toBe(defaultInitialization);
expect(_getDefaultInitialization()).toEqual(defaultInitialization);
_setDefaultInitialization(newDefaultInitialization);
expect(_getDefaultInitialization()).toEqual({
...defaultInitialization,
...newDefaultInitialization,
elements: {
...defaultInitialization.elements,
...newDefaultInitialization.elements,
},
cancel: {
...defaultInitialization.cancel,
...newDefaultInitialization.cancel,
},
});
});
});
describe('addListener', () => {
test('with scrollbarsHidingPlugin registered before environment was created', async () => {
const { getPlugins } = await import('plugins');
(getPlugins as jest.Mock).mockImplementation(() => ({
[scrollbarsHidingPluginName]: scrollbarsHidingPlugin[scrollbarsHidingPluginName],
}));
const { _addListener } = getEnv();
const listener = jest.fn();
_addListener(listener);
window.dispatchEvent(new Event('resize'));
expect(listener).toHaveBeenCalledTimes(1);
});
test('with scrollbarsHidingPlugin registered after environment was created', async () => {
const { _addListener } = getEnv();
const listener = jest.fn();
_addListener(listener);
const { getPlugins } = await import('plugins');
(getPlugins as jest.Mock).mockImplementation(() => ({
[scrollbarsHidingPluginName]: scrollbarsHidingPlugin[scrollbarsHidingPluginName],
}));
window.dispatchEvent(new Event('resize'));
expect(listener).toHaveBeenCalledTimes(1);
});
test('without scrollbarsHidingPlugin', () => {
const { _addListener } = getEnv();
const listener = jest.fn();
_addListener(listener);
window.dispatchEvent(new Event('resize'));
expect(listener).toHaveBeenCalledTimes(0);
});
});
});
@@ -0,0 +1,587 @@
import {
staticInitializationElement,
dynamicInitializationElement,
cancelInitialization,
Initialization,
} from 'initialization';
import { getEnvironment } from 'environment';
jest.mock('environment', () => ({
getEnvironment: jest.fn(() => jest.requireActual('environment').getEnvironment()),
}));
const createDiv = () => document.createElement('div');
describe('initialization', () => {
describe('staticInitializationElement', () => {
test('defined', () => {
const args: [a: boolean, b: string] = [true, ''];
const fallbackElm = createDiv();
const defaultElm = createDiv();
const elm = createDiv();
const fallbackElmFn = jest.fn(() => fallbackElm);
const elmFn = jest.fn(() => elm);
const nullFn = jest.fn(() => null);
const falseFn = jest.fn<false, any[]>(() => false);
const values = {
elm: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
elm
),
(result: HTMLElement) => expect(result).toBe(elm),
],
null: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
null
),
(result: HTMLElement) => expect(result).toBe(fallbackElm),
],
false: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
false
),
(result: HTMLElement) => expect(result).toBe(fallbackElm),
],
undefined: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
undefined
),
(result: HTMLElement) => expect(result).toBe(defaultElm),
],
};
const fns = {
elm: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
elmFn
),
(result: HTMLElement) => {
expect(result).toBe(elm);
expect(elmFn).toHaveBeenCalledTimes(1);
expect(elmFn).toHaveBeenLastCalledWith(...args);
},
],
null: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
nullFn
),
(result: HTMLElement) => {
expect(result).toBe(fallbackElm);
expect(nullFn).toHaveBeenCalledTimes(1);
expect(nullFn).toHaveBeenLastCalledWith(...args);
},
],
false: [
staticInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
falseFn
),
(result: HTMLElement) => {
expect(result).toBe(fallbackElm);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
};
Object.keys(values).forEach((key) => {
const [result, assertion] = values[key];
assertion(result);
});
Object.keys(fns).forEach((key) => {
const [result, assertion] = fns[key];
assertion(result);
});
});
test('default', () => {
const args: [a: boolean, b: string] = [true, ''];
const fallbackElm = createDiv();
const elm = createDiv();
const fallbackElmFn = jest.fn(() => fallbackElm);
const elmFn = jest.fn(() => elm);
const nullFn = jest.fn(() => null);
const falseFn = jest.fn<false, any[]>(() => false);
const values = {
elm: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, elm),
(result: HTMLElement) => expect(result).toBe(elm),
],
null: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, null),
(result: HTMLElement) => expect(result).toBe(fallbackElm),
],
false: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, false),
(result: HTMLElement) => expect(result).toBe(fallbackElm),
],
};
const fns = {
elm: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, elmFn),
(result: HTMLElement) => {
expect(result).toBe(elm);
expect(elmFn).toHaveBeenCalledTimes(1);
expect(elmFn).toHaveBeenLastCalledWith(...args);
},
],
null: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, nullFn),
(result: HTMLElement) => {
expect(result).toBe(fallbackElm);
expect(nullFn).toHaveBeenCalledTimes(1);
expect(nullFn).toHaveBeenLastCalledWith(...args);
},
],
false: [
staticInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, falseFn),
(result: HTMLElement) => {
expect(result).toBe(fallbackElm);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
};
Object.keys(values).forEach((key) => {
const [result, assertion] = values[key];
assertion(result);
});
Object.keys(fns).forEach((key) => {
const [result, assertion] = fns[key];
assertion(result);
});
});
});
describe('dynamicInitializationElement', () => {
test('defined', () => {
const args: [a: boolean, b: string] = [true, ''];
const fallbackElm = createDiv();
const defaultElm = createDiv();
const elm = createDiv();
const fallbackElmFn = jest.fn(() => fallbackElm);
const elmFn = jest.fn(() => elm);
const snullFn = jest.fn(() => null);
const falseFn = jest.fn<false, any[]>(() => false);
const trueFn = jest.fn<true, any[]>(() => true);
const values = {
elm: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
elm
),
(result: HTMLElement | false) => expect(result).toBe(elm),
],
null: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
null
),
(result: HTMLElement | false) => expect(result).toBe(false),
],
false: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
false
),
(result: HTMLElement | false) => expect(result).toBe(false),
],
true: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
true
),
(result: HTMLElement | false) => expect(result).toBe(fallbackElm),
],
undefined: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
undefined
),
(result: HTMLElement | false) => expect(result).toBe(defaultElm),
],
};
const fns = {
elm: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
elmFn
),
(result: HTMLElement | false) => {
expect(result).toBe(elm);
expect(elmFn).toHaveBeenCalledTimes(1);
expect(elmFn).toHaveBeenLastCalledWith(...args);
},
],
null: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
snullFn
),
(result: HTMLElement | false) => {
expect(result).toBe(false);
expect(snullFn).toHaveBeenCalledTimes(1);
expect(snullFn).toHaveBeenLastCalledWith(...args);
},
],
false: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
falseFn
),
(result: HTMLElement | false) => {
expect(result).toBe(false);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
true: [
dynamicInitializationElement<[a: boolean, b: string]>(
args,
fallbackElmFn,
defaultElm,
trueFn
),
(result: HTMLElement | false) => {
expect(result).toBe(fallbackElm);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
};
Object.keys(values).forEach((key) => {
const [result, assertion] = values[key];
assertion(result);
});
Object.keys(fns).forEach((key) => {
const [result, assertion] = fns[key];
assertion(result);
});
});
test('default', () => {
const args: [a: boolean, b: string] = [true, ''];
const fallbackElm = createDiv();
const elm = createDiv();
const fallbackElmFn = jest.fn(() => fallbackElm);
const elmFn = jest.fn(() => elm);
const nullFn = jest.fn(() => null);
const falseFn = jest.fn<false, any[]>(() => false);
const trueFn = jest.fn<true, any[]>(() => true);
const values = {
elm: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, elm),
(result: HTMLElement | false) => expect(result).toBe(elm),
],
null: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, null),
(result: HTMLElement | false) => expect(result).toBe(false),
],
false: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, false),
(result: HTMLElement | false) => expect(result).toBe(false),
],
true: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, true),
(result: HTMLElement | false) => expect(result).toBe(fallbackElm),
],
};
const fns = {
elm: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, elmFn),
(result: HTMLElement | false) => {
expect(result).toBe(elm);
expect(elmFn).toHaveBeenCalledTimes(1);
expect(elmFn).toHaveBeenLastCalledWith(...args);
},
],
null: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, nullFn),
(result: HTMLElement | false) => {
expect(result).toBe(false);
expect(nullFn).toHaveBeenCalledTimes(1);
expect(nullFn).toHaveBeenLastCalledWith(...args);
},
],
false: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, falseFn),
(result: HTMLElement | false) => {
expect(result).toBe(false);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
true: [
dynamicInitializationElement<[a: boolean, b: string]>(args, fallbackElmFn, trueFn),
(result: HTMLElement | false) => {
expect(result).toBe(fallbackElm);
expect(falseFn).toHaveBeenCalledTimes(1);
expect(falseFn).toHaveBeenLastCalledWith(...args);
},
],
};
Object.keys(values).forEach((key) => {
const [result, assertion] = values[key];
assertion(result);
});
Object.keys(fns).forEach((key) => {
const [result, assertion] = fns[key];
assertion(result);
});
});
});
describe('cancelInitialization', () => {
describe('nativeScrollbarsOverlaid', () => {
test('defined', () => {
(
[
{
nativeScrollbarsOverlaid: false,
body: false,
},
{
nativeScrollbarsOverlaid: true,
body: false,
},
] as Initialization['cancel'][]
).forEach((defaultCancelInitialization) => {
[
{ nativeScrollbarsOverlaid: false },
{ nativeScrollbarsOverlaid: true },
{ nativeScrollbarsOverlaid: undefined },
].forEach((initializationValue) => {
[
{
_nativeScrollbarsOverlaid: {
x: false,
y: false,
},
},
{
_nativeScrollbarsOverlaid: {
x: false,
y: true,
},
},
{
_nativeScrollbarsOverlaid: {
x: true,
y: true,
},
},
].forEach((env) => {
(getEnvironment as jest.Mock).mockImplementation(() => ({
...jest.requireActual('environment').getEnvironment(),
...env,
}));
const hasOverlaidScrollbars =
env._nativeScrollbarsOverlaid.x || env._nativeScrollbarsOverlaid.y;
const expected =
hasOverlaidScrollbars &&
(initializationValue.nativeScrollbarsOverlaid ??
defaultCancelInitialization.nativeScrollbarsOverlaid);
expect(
cancelInitialization(false, defaultCancelInitialization, initializationValue)
).toEqual(expected);
});
});
});
});
test('default', () => {
(
[
{
nativeScrollbarsOverlaid: false,
body: false,
},
{
nativeScrollbarsOverlaid: true,
body: false,
},
] as Initialization['cancel'][]
).forEach((defaultCancelInitialization) => {
[
{
_nativeScrollbarsOverlaid: {
x: false,
y: false,
},
},
{
_nativeScrollbarsOverlaid: {
x: false,
y: true,
},
},
{
_nativeScrollbarsOverlaid: {
x: true,
y: true,
},
},
].forEach((env) => {
(getEnvironment as jest.Mock).mockImplementation(() => ({
...jest.requireActual('environment').getEnvironment(),
...env,
}));
const hasOverlaidScrollbars =
env._nativeScrollbarsOverlaid.x || env._nativeScrollbarsOverlaid.y;
const expected =
hasOverlaidScrollbars && defaultCancelInitialization.nativeScrollbarsOverlaid;
expect(cancelInitialization(false, defaultCancelInitialization)).toEqual(expected);
expect(cancelInitialization(false, defaultCancelInitialization, undefined)).toEqual(
expected
);
expect(cancelInitialization(false, defaultCancelInitialization, null)).toEqual(
expected
);
expect(cancelInitialization(false, defaultCancelInitialization, false)).toEqual(
expected
);
});
});
});
});
describe('body', () => {
test('defined', () => {
(
[
{
nativeScrollbarsOverlaid: false,
body: false,
},
{
nativeScrollbarsOverlaid: false,
body: true,
},
{
nativeScrollbarsOverlaid: false,
body: null,
},
] as Initialization['cancel'][]
).forEach((defaultCancelInitialization) => {
[{ body: false }, { body: true }, { body: null }, { body: undefined }].forEach(
(initializationValue) => {
[{ _nativeScrollbarsHiding: false }, { _nativeScrollbarsHiding: true }].forEach(
(env) => {
[false, true].forEach((isBody) => {
(getEnvironment as jest.Mock).mockImplementation(() => ({
...jest.requireActual('environment').getEnvironment(),
...env,
}));
const defaultBody = defaultCancelInitialization.body;
const bodyValue = initializationValue.body;
const finalBody = bodyValue === undefined ? defaultBody : bodyValue;
const expected =
isBody && (finalBody === null ? !env._nativeScrollbarsHiding : finalBody);
expect(
cancelInitialization(isBody, defaultCancelInitialization, initializationValue)
).toEqual(expected);
});
}
);
}
);
});
});
test('default', () => {
(
[
{
nativeScrollbarsOverlaid: false,
body: false,
},
{
nativeScrollbarsOverlaid: false,
body: true,
},
{
nativeScrollbarsOverlaid: false,
body: null,
},
] as Initialization['cancel'][]
).forEach((defaultCancelInitialization) => {
[{ _nativeScrollbarsHiding: false }, { _nativeScrollbarsHiding: true }].forEach((env) => {
[false, true].forEach((isBody) => {
(getEnvironment as jest.Mock).mockImplementation(() => ({
...jest.requireActual('environment').getEnvironment(),
...env,
}));
const defaultBody = defaultCancelInitialization.body;
const expected =
isBody && (defaultBody === null ? !env._nativeScrollbarsHiding : defaultBody);
expect(cancelInitialization(isBody, defaultCancelInitialization)).toEqual(expected);
expect(cancelInitialization(isBody, defaultCancelInitialization, undefined)).toEqual(
expected
);
expect(cancelInitialization(isBody, defaultCancelInitialization, null)).toEqual(
expected
);
expect(cancelInitialization(isBody, defaultCancelInitialization, false)).toEqual(
expected
);
});
});
});
});
});
});
});
@@ -1,5 +1,5 @@
import { noop, debounce } from 'support/utils/function'; import { noop, debounce, selfClearTimeout } from 'support/utils/function';
import { rAF, setT } from 'support/compatibility/apis'; import { rAF, cAF, setT, clearT } from 'support/compatibility/apis';
jest.useFakeTimers(); jest.useFakeTimers();
@@ -318,4 +318,105 @@ describe('function', () => {
}); });
}); });
}); });
describe('selfClearTimeout', () => {
test('without timeout', () => {
let i = 0;
const [timeout, clear] = selfClearTimeout();
expect(rAF).not.toHaveBeenCalled();
expect(cAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
expect(clearT).not.toHaveBeenCalled();
timeout(() => {
i += 1;
});
clear();
expect(rAF).toHaveBeenCalledTimes(1);
expect(cAF).toHaveBeenCalledTimes(2);
expect(setT).not.toHaveBeenCalled();
expect(clearT).not.toHaveBeenCalled();
expect(i).toBe(0);
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
jest.runAllTimers();
expect(i).toBe(1);
});
test('with timeout', () => {
let i = 0;
const [timeout, clear] = selfClearTimeout(100);
expect(rAF).not.toHaveBeenCalled();
expect(cAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
expect(clearT).not.toHaveBeenCalled();
timeout(() => {
i += 1;
});
clear();
expect(rAF).not.toHaveBeenCalled();
expect(cAF).not.toHaveBeenCalled();
expect(setT).toHaveBeenCalledTimes(1);
expect(clearT).toHaveBeenCalledTimes(2);
expect(i).toBe(0);
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
jest.runAllTimers();
expect(i).toBe(1);
});
test('with timeout function', () => {
let i = 0;
const [timeout, clear] = selfClearTimeout(() => 100);
expect(rAF).not.toHaveBeenCalled();
expect(cAF).not.toHaveBeenCalled();
expect(setT).not.toHaveBeenCalled();
expect(clearT).not.toHaveBeenCalled();
timeout(() => {
i += 1;
});
clear();
expect(rAF).not.toHaveBeenCalled();
expect(cAF).not.toHaveBeenCalled();
expect(setT).toHaveBeenCalledTimes(1);
expect(clearT).toHaveBeenCalledTimes(2);
expect(i).toBe(0);
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
timeout(() => {
i += 1;
});
jest.runAllTimers();
expect(i).toBe(1);
});
});
}); });