mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-17 07:49:39 +03:00
add initialization and environment unit tests
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user