Files
OverlayScrollbars/packages/overlayscrollbars/test/jest-jsdom/overlayscrollbars.test.ts
T

651 lines
21 KiB
TypeScript

import { defaultOptions } from '~/options';
import { assignDeep } from '~/support';
import { OptionsValidationPlugin } from '~/plugins';
import { OverlayScrollbars as originalOverlayScrollbars } from '~/overlayscrollbars';
import type { PartialOptions } from '~/options';
jest.useFakeTimers();
jest.mock('~/support/compatibility/apis', () => {
const originalModule = jest.requireActual('~/support/compatibility/apis');
const mockRAF = (arg: any) => setTimeout(arg, 0);
return {
...originalModule,
// @ts-ignore
rAF: jest.fn().mockImplementation((...args) => mockRAF(...args)),
// @ts-ignore
cAF: jest.fn().mockImplementation((...args) => clearTimeout(...args)),
// @ts-ignore
setT: jest.fn().mockImplementation((...args) => setTimeout(...args)),
// @ts-ignore
clearT: jest.fn().mockImplementation((...args) => clearTimeout(...args)),
};
});
const bodyElm = document.body;
const div = document.createElement('div');
bodyElm.append(div);
let OverlayScrollbars = originalOverlayScrollbars;
describe('overlayscrollbars', () => {
beforeEach(async () => {
jest.resetModules();
({ OverlayScrollbars } = await import('~/overlayscrollbars'));
});
afterEach(() => {
const instance = OverlayScrollbars(div);
if (OverlayScrollbars.valid(instance)) {
instance.destroy();
}
});
describe('instance', () => {
describe('initialization', () => {
describe('initialization completed', () => {
[div, { target: div }].forEach((init) => {
describe(`as ${init === div ? 'element' : 'object'}`, () => {
test('without options', () => {
const osInstance = OverlayScrollbars(init);
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
expect(div.children.length).toBe(0);
});
test('with empty options', () => {
const osInstance = OverlayScrollbars(init, {});
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
expect(div.children.length).not.toBe(0);
expect(osInstance.options()).not.toBe(defaultOptions);
expect(osInstance.options()).toEqual(defaultOptions);
});
test('with custom options', () => {
const customOptions: PartialOptions = {
paddingAbsolute: false,
showNativeOverlaidScrollbars: true,
overflow: {
x: 'hidden',
},
update: {
ignoreMutation: () => true,
elementEvents: null,
},
scrollbars: {
pointers: null,
autoHideDelay: 0,
},
};
const osInstance = OverlayScrollbars(init, customOptions);
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
expect(div.children.length).not.toBe(0);
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
});
test('with event listeners', () => {
const initialized = jest.fn();
const updated = jest.fn();
const destroyed = jest.fn();
const osInstance = OverlayScrollbars(
init,
{},
{
initialized,
updated,
destroyed,
}
);
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
expect(div.children.length).not.toBe(0);
expect(initialized).toHaveBeenCalledTimes(1);
expect(initialized).toHaveBeenCalledWith(osInstance);
expect(updated).toHaveBeenCalledTimes(1);
expect(updated).toHaveBeenCalledWith(osInstance, expect.any(Object));
expect(destroyed).not.toHaveBeenCalled();
osInstance.destroy();
expect(destroyed).toHaveBeenCalledTimes(1);
expect(destroyed).toHaveBeenCalledWith(osInstance, false);
});
});
});
});
describe('initialization canceled', () => {
test('without options', () => {
const osInstance = OverlayScrollbars({
target: div,
cancel: { nativeScrollbarsOverlaid: true },
});
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
expect(div.children.length).toBe(0);
});
test('with empty options', () => {
const osInstance = OverlayScrollbars(
{
target: div,
cancel: { nativeScrollbarsOverlaid: true },
},
{}
);
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
expect(div.children.length).toBe(0);
});
test('with event listeners', () => {
const initialized = jest.fn();
const updated = jest.fn();
const destroyed = jest.fn();
const osInstance = OverlayScrollbars(
{
target: div,
cancel: { nativeScrollbarsOverlaid: true },
},
{},
{
initialized,
updated,
destroyed,
}
);
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
expect(div.children.length).toBe(0);
expect(initialized).not.toHaveBeenCalled();
expect(updated).not.toHaveBeenCalled();
expect(destroyed).toHaveBeenCalledTimes(1);
expect(destroyed).toHaveBeenCalledWith(osInstance, true);
});
});
});
describe('elements', () => {
test('get elements', () => {
const osInstance = OverlayScrollbars(div, {});
const elements = osInstance.elements();
const elementsObj = {
target: div,
host: div,
padding: expect.any(HTMLElement),
viewport: expect.any(HTMLElement),
content: expect.any(HTMLElement),
scrollOffsetElement: expect.any(HTMLElement),
scrollEventElement: expect.any(HTMLElement),
scrollbarHorizontal: {
scrollbar: expect.any(HTMLElement),
track: expect.any(HTMLElement),
handle: expect.any(HTMLElement),
clone: expect.any(Function),
},
scrollbarVertical: {
scrollbar: expect.any(HTMLElement),
track: expect.any(HTMLElement),
handle: expect.any(HTMLElement),
clone: expect.any(Function),
},
};
expect(elements).not.toBe(osInstance.elements());
// clone function identity is always different
expect(
assignDeep({}, elements, {
scrollbarHorizontal: {
clone: null,
},
scrollbarVertical: {
clone: null,
},
})
).toEqual(
assignDeep({}, osInstance.elements(), {
scrollbarHorizontal: {
clone: null,
},
scrollbarVertical: {
clone: null,
},
})
);
expect(elements).toEqual(elementsObj);
osInstance.destroy();
expect(elements).toEqual(elementsObj);
});
test('clone scrollbars', () => {
const osInstance = OverlayScrollbars(div, {});
const elements = osInstance.elements();
const hClone = elements.scrollbarHorizontal.clone();
const vClone = elements.scrollbarVertical.clone();
expect(hClone).toEqual({
scrollbar: expect.any(HTMLElement),
track: expect.any(HTMLElement),
handle: expect.any(HTMLElement),
});
expect(vClone).toEqual({
scrollbar: expect.any(HTMLElement),
track: expect.any(HTMLElement),
handle: expect.any(HTMLElement),
});
div.append(hClone.scrollbar);
div.append(vClone.scrollbar);
osInstance.destroy();
expect(div.innerHTML).toBe('');
});
});
describe('options', () => {
[false, true].forEach((withValidationPlugin) => {
describe(`${withValidationPlugin ? 'with' : 'without'} optionsValidationPlugin`, () => {
beforeEach(() => {
if (withValidationPlugin) {
OverlayScrollbars.plugin(OptionsValidationPlugin);
}
});
test('equality', () => {
const osInstance = OverlayScrollbars(div, {});
expect(osInstance.options()).not.toBe(defaultOptions);
expect(osInstance.options()).toEqual(defaultOptions);
OverlayScrollbars(div)!.destroy();
});
test('initial options', () => {
const customOptions: PartialOptions = {
paddingAbsolute: !defaultOptions.paddingAbsolute,
overflow: { x: 'hidden' },
};
const osInstance = OverlayScrollbars(div, customOptions);
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
});
test('changed options', () => {
const customOptions: PartialOptions = {
paddingAbsolute: !defaultOptions.paddingAbsolute,
overflow: { x: 'hidden' },
};
const customOptions2: PartialOptions = {
overflow: { y: 'hidden' },
};
const osInstance = OverlayScrollbars(div, {});
expect(osInstance.options(customOptions)).toEqual(
assignDeep({}, defaultOptions, customOptions)
);
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
expect(osInstance.options(customOptions2)).toEqual(
assignDeep({}, defaultOptions, customOptions, customOptions2)
);
expect(osInstance.options()).toEqual(
assignDeep({}, defaultOptions, customOptions, customOptions2)
);
osInstance.destroy();
});
test('changed options pure', () => {
const customOptions: PartialOptions = {
paddingAbsolute: !defaultOptions.paddingAbsolute,
overflow: { x: 'hidden' },
};
const customOptions2: PartialOptions = {
overflow: { y: 'hidden' },
};
const osInstance = OverlayScrollbars(div, {});
expect(osInstance.options(customOptions, true)).toEqual(
assignDeep({}, defaultOptions, customOptions)
);
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
expect(osInstance.options(customOptions2, true)).toEqual(
assignDeep({}, defaultOptions, customOptions2)
);
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions2));
osInstance.destroy();
});
test('unchanged wont trigger update', () => {
const updated = jest.fn();
const initialOpts: PartialOptions = {
paddingAbsolute: true,
overflow: {
y: 'hidden',
},
};
const osInstance = OverlayScrollbars(div, initialOpts, {
updated,
});
expect(updated).toHaveBeenCalledTimes(1);
osInstance.options(initialOpts);
osInstance.options(initialOpts);
osInstance.options({});
osInstance.options({});
expect(updated).toHaveBeenCalledTimes(1);
osInstance.options(initialOpts, true);
osInstance.options(initialOpts, true);
expect(updated).toHaveBeenCalledTimes(1);
osInstance.options({}, true);
osInstance.options({}, true);
expect(updated).toHaveBeenCalledTimes(2);
osInstance.options(defaultOptions, true);
osInstance.options(defaultOptions, true);
expect(updated).toHaveBeenCalledTimes(2);
osInstance.options(initialOpts);
osInstance.options(initialOpts);
expect(updated).toHaveBeenCalledTimes(3);
osInstance.options(defaultOptions);
osInstance.options(defaultOptions);
expect(updated).toHaveBeenCalledTimes(4);
osInstance.options(initialOpts, true);
osInstance.options(initialOpts, true);
expect(updated).toHaveBeenCalledTimes(5);
osInstance.options(defaultOptions, true);
osInstance.options(defaultOptions, true);
expect(updated).toHaveBeenCalledTimes(6);
});
});
});
});
test('on', () => {
const onInitialized = jest.fn();
const onUpdated = jest.fn();
const onUpdated2 = jest.fn();
const onDestroyed = jest.fn();
const onScroll = jest.fn();
const osInstance = OverlayScrollbars(div, {});
osInstance.on('initialized', onInitialized);
osInstance.on('updated', [onUpdated, onUpdated, onUpdated2]);
osInstance.on('destroyed', onDestroyed);
osInstance.on('scroll', onScroll);
expect(onInitialized).not.toHaveBeenCalled();
expect(onUpdated).not.toHaveBeenCalled();
expect(onUpdated2).not.toHaveBeenCalled();
expect(onScroll).not.toHaveBeenCalled();
osInstance.elements().scrollEventElement.dispatchEvent(new Event('scroll'));
expect(onScroll).toHaveBeenCalledTimes(1);
expect(onScroll).toHaveBeenLastCalledWith(osInstance, expect.any(Object));
osInstance.update();
expect(onUpdated).not.toHaveBeenCalled();
expect(onUpdated2).not.toHaveBeenCalled();
osInstance.update(true);
expect(onUpdated).toHaveBeenCalledTimes(1);
expect(onUpdated2).toHaveBeenCalledTimes(1);
expect(onUpdated).toHaveBeenLastCalledWith(osInstance, expect.any(Object));
expect(onUpdated2).toHaveBeenLastCalledWith(osInstance, expect.any(Object));
expect(onDestroyed).not.toHaveBeenCalled();
OverlayScrollbars(div)!.destroy();
expect(onDestroyed).toHaveBeenCalledTimes(1);
expect(onDestroyed).toHaveBeenLastCalledWith(osInstance, false);
// after destruction no further events are triggered
osInstance.update(true);
osInstance.elements().scrollEventElement.dispatchEvent(new Event('scroll'));
expect(onUpdated).toHaveBeenCalledTimes(1);
expect(onUpdated2).toHaveBeenCalledTimes(1);
expect(onScroll).toHaveBeenCalledTimes(1);
});
test('off', () => {
const onInitialized = jest.fn();
const onUpdated = jest.fn();
const onUpdated2 = jest.fn();
const onDestroyed = jest.fn();
const onScroll = jest.fn();
expect(onInitialized).not.toHaveBeenCalled();
const osInstance = OverlayScrollbars(
div,
{},
{
initialized: onInitialized,
updated: [onUpdated, onUpdated, onUpdated2],
destroyed: onDestroyed,
scroll: onScroll,
}
);
expect(onInitialized).toHaveBeenCalledTimes(1);
expect(onInitialized).toHaveBeenLastCalledWith(osInstance);
expect(onUpdated).toHaveBeenCalledTimes(1);
expect(onUpdated2).toHaveBeenCalledTimes(1);
expect(onScroll).not.toHaveBeenCalled();
osInstance.elements().scrollEventElement.dispatchEvent(new Event('scroll'));
expect(onUpdated).toHaveBeenCalledTimes(1);
osInstance.off('scroll', onScroll);
osInstance.elements().scrollEventElement.dispatchEvent(new Event('scroll'));
expect(onUpdated).toHaveBeenCalledTimes(1);
osInstance.update(true);
expect(onUpdated).toHaveBeenCalledTimes(2);
expect(onUpdated2).toHaveBeenCalledTimes(2);
osInstance.off('updated', onUpdated2);
osInstance.update(true);
expect(onUpdated).toHaveBeenCalledTimes(3);
expect(onUpdated2).toHaveBeenCalledTimes(2);
osInstance.on('updated', onUpdated2);
osInstance.update(true);
expect(onUpdated).toHaveBeenCalledTimes(4);
expect(onUpdated2).toHaveBeenCalledTimes(3);
osInstance.off('updated', [onUpdated, onUpdated2]);
osInstance.update(true);
expect(onUpdated).toHaveBeenCalledTimes(4);
expect(onUpdated2).toHaveBeenCalledTimes(3);
osInstance.off('destroyed', onDestroyed);
expect(onDestroyed).not.toHaveBeenCalled();
osInstance.destroy();
expect(onDestroyed).not.toHaveBeenCalled();
});
test('state', () => {
const osInstance = OverlayScrollbars(div, {});
const state = osInstance.state();
const stateObj = {
overflowEdge: { x: 0, y: 0 },
overflowAmount: { x: 0, y: 0 },
overflowStyle: { x: '', y: '' },
hasOverflow: { x: false, y: false },
padding: { t: 0, r: 0, b: 0, l: 0 },
paddingAbsolute: false,
directionRTL: false,
destroyed: false,
};
expect(state).not.toBe(osInstance.state());
expect(state).toEqual(osInstance.state());
expect(state).toEqual(stateObj);
osInstance.options({ paddingAbsolute: true });
expect(osInstance.state()).toEqual(
expect.objectContaining({
paddingAbsolute: true,
})
);
osInstance.destroy();
expect(osInstance.state()).toEqual(
expect.objectContaining({
destroyed: true,
})
);
});
test('update', () => {
const osInstance = OverlayScrollbars(div, {});
expect(osInstance.update()).toBe(false);
expect(osInstance.update(false)).toBe(false);
expect(osInstance.update(true)).toBe(true);
// host mutation
div.style.cursor = 'pointer';
expect(osInstance.update()).toBe(true);
});
test('environment resize listener', () => {
const updated = jest.fn();
OverlayScrollbars(
div,
{},
{
updated,
}
);
expect(updated).toHaveBeenCalledTimes(1);
window.dispatchEvent(new Event('resize'));
expect(updated).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(updated).toHaveBeenCalledTimes(2);
});
});
describe('static', () => {
test('plugin', () => {
expect(OverlayScrollbars.plugin).toEqual(expect.any(Function));
});
test('env', () => {
const env = OverlayScrollbars.env();
const envObj = {
scrollbarsSize: { x: 0, y: 0 },
scrollbarsOverlaid: { x: true, y: true },
scrollbarsHiding: false,
rtlScrollBehavior: { i: true, n: false },
flexboxGlue: true,
cssCustomProperties: false,
staticDefaultInitialization: expect.any(Object),
staticDefaultOptions: expect.any(Object),
getDefaultInitialization: expect.any(Function),
setDefaultInitialization: expect.any(Function),
getDefaultOptions: expect.any(Function),
setDefaultOptions: expect.any(Function),
};
expect(env).not.toBe(OverlayScrollbars.env());
expect(env).toEqual(OverlayScrollbars.env());
expect(env).toEqual(envObj);
});
test('valid', () => {
expect(OverlayScrollbars.valid(true)).toBe(false);
expect(OverlayScrollbars.valid(false)).toBe(false);
expect(OverlayScrollbars.valid('')).toBe(false);
expect(OverlayScrollbars.valid(123)).toBe(false);
expect(OverlayScrollbars.valid({})).toBe(false);
expect(OverlayScrollbars.valid(div)).toBe(false);
expect(OverlayScrollbars.valid(setTimeout)).toBe(false);
expect(OverlayScrollbars.valid(OverlayScrollbars(div))).toBe(false);
const osInstance = OverlayScrollbars(div, {});
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
osInstance.destroy();
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
});
});
test('plugins', () => {
const staticPluginFn = jest.fn();
const staticObjPluginFn = jest.fn();
const instanceObjPluginFn = jest.fn();
const staticPlugin = {
staticPlugin: {
staticFn: staticPluginFn,
},
};
expect(
OverlayScrollbars.plugin([
{
expect: (staticObj, instanceObj) => {
if (instanceObj) {
expect(staticObj).toBe(undefined);
expect(OverlayScrollbars.valid(instanceObj)).toBe(true);
}
if (staticObj) {
expect(staticObj).toBe(OverlayScrollbars);
expect(instanceObj).toBe(undefined);
}
},
},
{
staticPlugin,
staticObjPlugin: (staticObj, instanceObj) => {
if (staticObj) {
expect(instanceObj).toBe(undefined);
// @ts-ignore
staticObj.staticObjPluginFn = staticObjPluginFn;
}
},
instanceObjPlugin: (_, instanceObj) => {
if (instanceObj) {
// @ts-ignore
instanceObj.instanceObjPluginFn = instanceObjPluginFn;
}
},
},
])
).toBe(undefined);
expect(staticPluginFn).not.toHaveBeenCalled();
// @ts-ignore
staticPlugin.staticPlugin.staticFn();
expect(staticPluginFn).toHaveBeenCalledTimes(1);
// staticObj plugin must be available before any initialization
expect(staticObjPluginFn).not.toHaveBeenCalled();
// @ts-ignore
OverlayScrollbars.staticObjPluginFn();
expect(staticObjPluginFn).toHaveBeenCalledTimes(1);
const osInstance = OverlayScrollbars(div, {});
expect(instanceObjPluginFn).not.toHaveBeenCalled();
// @ts-ignore
osInstance.instanceObjPluginFn();
expect(instanceObjPluginFn).toHaveBeenCalledTimes(1);
});
});