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 { getEnvironment } from 'environment';
import { DeepPartial } from 'typings';
import { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { DeepPartial } from 'typings';
type StaticInitialization = HTMLElement | false | null;
type DynamicInitialization = HTMLElement | boolean | null;
type FallbackInitializtationElement<
InitElm extends StaticInitializationElement<any> | DynamicInitializationElement<any>
> = Extract<InitElm, (...args: any[]) => any> extends (...args: infer P) => any
type FallbackStaticInitializtationElement<Args extends any[]> = Extract<
StaticInitializationElement<Args>,
(...args: Args) => 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
: never;
@@ -59,30 +64,30 @@ export type InitializationTarget = InitializationTargetElement | InitializationT
const resolveInitialization = <T>(value: any, args: any): T =>
isFunction(value) ? value.apply(0, args) : value;
export const staticInitializationElement = <T extends StaticInitializationElement<any>>(
args: Parameters<Extract<T, (...initializationFnArgs: any[]) => any>>,
fallbackStaticInitializationElement: FallbackInitializtationElement<T>,
defaultStaticInitializationElementStrategy: T,
staticInitializationElementValue?: T
export const staticInitializationElement = <Args extends any[]>(
args: Args,
fallbackStaticInitializationElement: FallbackStaticInitializtationElement<Args>,
defaultStaticInitializationElement: StaticInitializationElement<Args>,
staticInitializationElementValue?: StaticInitializationElement<Args>
): HTMLElement => {
const staticInitialization = isUndefined(staticInitializationElementValue)
? defaultStaticInitializationElementStrategy
? defaultStaticInitializationElement
: staticInitializationElementValue;
const resolvedInitialization = resolveInitialization<StaticInitialization>(
staticInitialization,
args
);
return resolvedInitialization || fallbackStaticInitializationElement();
return resolvedInitialization || fallbackStaticInitializationElement.apply(0, args);
};
export const dynamicInitializationElement = <T extends DynamicInitializationElement<any>>(
args: Parameters<Extract<T, (...initializationFnArgs: any[]) => any>>,
fallbackDynamicInitializationElement: FallbackInitializtationElement<T>,
defaultDynamicInitializationElementStrategy: T,
dynamicInitializationElementValue?: T
export const dynamicInitializationElement = <Args extends any[]>(
args: Args,
fallbackDynamicInitializationElement: FallbackDynamicInitializtationElement<Args>,
defaultDynamicInitializationElement: DynamicInitializationElement<Args>,
dynamicInitializationElementValue?: DynamicInitializationElement<Args>
): HTMLElement | false => {
const dynamicInitialization = isUndefined(dynamicInitializationElementValue)
? defaultDynamicInitializationElementStrategy
? defaultDynamicInitializationElement
: dynamicInitializationElementValue;
const resolvedInitialization = resolveInitialization<DynamicInitialization>(
dynamicInitialization,
@@ -92,20 +97,19 @@ export const dynamicInitializationElement = <T extends DynamicInitializationElem
!!resolvedInitialization &&
(isHTMLElement(resolvedInitialization)
? resolvedInitialization
: fallbackDynamicInitializationElement())
: fallbackDynamicInitializationElement.apply(0, args))
);
};
export const cancelInitialization = (
cancelInitializationValue: DeepPartial<Initialization['cancel']> | false | null | undefined,
structureSetupElements: StructureSetupElementsObj
isBody: boolean,
defaultCancelInitialization: Initialization['cancel'],
cancelInitializationValue?: DeepPartial<Initialization['cancel']> | false | null | undefined
): boolean => {
const { nativeScrollbarsOverlaid, body } = cancelInitializationValue || {};
const { _isBody } = structureSetupElements;
const { _getDefaultInitialization, _nativeScrollbarsOverlaid, _nativeScrollbarsHiding } =
getEnvironment();
const { _nativeScrollbarsOverlaid, _nativeScrollbarsHiding } = getEnvironment();
const { nativeScrollbarsOverlaid: defaultNativeScrollbarsOverlaid, body: defaultbody } =
_getDefaultInitialization().cancel;
defaultCancelInitialization;
const resolvedNativeScrollbarsOverlaid =
nativeScrollbarsOverlaid ?? defaultNativeScrollbarsOverlaid;
@@ -115,7 +119,7 @@ export const cancelInitialization = (
(_nativeScrollbarsOverlaid.x || _nativeScrollbarsOverlaid.y) &&
resolvedNativeScrollbarsOverlaid;
const finalDocumentScrollingElement =
_isBody &&
isBody &&
(isNull(resolvedDocumentScrollingElement)
? !_nativeScrollbarsHiding
: resolvedDocumentScrollingElement);
@@ -115,7 +115,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
options?: DeepPartial<Options>,
eventListeners?: InitialEventListeners
) => {
const { _getDefaultOptions, _addListener: addEnvListener } = getEnvironment();
const {
_getDefaultOptions,
_getDefaultInitialization,
_addListener: addEnvListener,
} = getEnvironment();
const plugins = getPlugins();
const targetIsElement = isHTMLElement(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);
return instance;
}
@@ -28,14 +28,14 @@ import {
getScrollbarHandleOffsetRatio,
} from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
import type {
Initialization,
InitializationTarget,
InitializationTargetElement,
InitializationTargetObject,
} from 'initialization';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { ScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
import type { StyleObject } from 'typings';
import { StructureSetupState } from 'setups';
import type { StructureSetupState } from 'setups';
export interface ScrollbarStructure {
_scrollbar: HTMLElement;
@@ -84,7 +84,7 @@ export const createScrollbarsSetupElements = (
const { scrollbars: scrollbarsInit } = (_targetIsElm ? {} : target) as InitializationTargetObject;
const { slot: initScrollbarsSlot } = scrollbarsInit || {};
const evaluatedScrollbarSlot = generalDynamicInitializationElement<
Initialization['scrollbars']['slot']
[InitializationTargetElement, HTMLElement, HTMLElement]
>([_target, _host, _viewport], () => _host, defaultInitScrollbarsSlot, initScrollbarsSlot);
const scrollbarStructureAddRemoveClass = (
scrollbarStructures: ScrollbarStructure[],
@@ -7,7 +7,7 @@ import {
runEachAndClear,
stopPropagation,
XY,
selfCancelTimeout,
selfClearTimeout,
parent,
closest,
rAF,
@@ -223,7 +223,7 @@ export const createScrollbarsSetupEvents =
isHorizontal
) => {
const { _scrollbar } = scrollbarStructure;
const [wheelTimeout, clearScrollTimeout] = selfCancelTimeout(333);
const [wheelTimeout, clearScrollTimeout] = selfClearTimeout(333);
const scrollByFn = !!scrollOffsetElm.scrollBy;
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 { createScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
import {
@@ -51,11 +51,11 @@ export const createScrollbarsSetup = (
const state = createState({});
const [getState] = state;
const [requestMouseMoveAnimationFrame, cancelMouseMoveAnimationFrame] = selfCancelTimeout();
const [requestScrollAnimationFrame, cancelScrollAnimationFrame] = selfCancelTimeout();
const [scrollTimeout, clearScrollTimeout] = selfCancelTimeout(100);
const [auotHideMoveTimeout, clearAutoHideTimeout] = selfCancelTimeout(100);
const [auotHideTimeout, clearAutoTimeout] = selfCancelTimeout(() => globalAutoHideDelay);
const [requestMouseMoveAnimationFrame, cancelMouseMoveAnimationFrame] = selfClearTimeout();
const [requestScrollAnimationFrame, cancelScrollAnimationFrame] = selfClearTimeout();
const [scrollTimeout, clearScrollTimeout] = selfClearTimeout(100);
const [auotHideMoveTimeout, clearAutoHideTimeout] = selfClearTimeout(100);
const [auotHideTimeout, clearAutoTimeout] = selfClearTimeout(() => globalAutoHideDelay);
const [elements, appendElements, destroyElements] = createScrollbarsSetupElements(
target,
structureSetupState._elements,
@@ -39,7 +39,6 @@ import {
dynamicInitializationElement as generalDynamicInitializationElement,
} from 'initialization';
import type {
Initialization,
InitializationTarget,
InitializationTargetElement,
InitializationTargetObject,
@@ -117,10 +116,10 @@ export const createStructureSetupElements = (
const isBody = targetElement === ownerDocument.body;
const wnd = ownerDocument.defaultView as Window;
const staticInitializationElement = generalStaticInitializationElement<
Initialization['elements']['viewport']
[InitializationTargetElement]
>.bind(0, [targetElement]);
const dynamicInitializationElement = generalDynamicInitializationElement<
Initialization['elements']['content']
[InitializationTargetElement]
>.bind(0, [targetElement]);
const viewportElement = staticInitializationElement(
createNewDiv,
@@ -30,7 +30,12 @@ export interface Debounced<FunctionToDebounce extends (...args: any) => any> {
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;
const setTFn = timeout ? setT : rAF!;
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 { rAF, setT } from 'support/compatibility/apis';
import { noop, debounce, selfClearTimeout } from 'support/utils/function';
import { rAF, cAF, setT, clearT } from 'support/compatibility/apis';
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);
});
});
});