mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-17 06:19:39 +03:00
improve plugin system & add and improve various tests
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
import { assignDeep, each, isObject, keys, isArray, hasOwnProperty, isFunction } from 'support';
|
||||
import {
|
||||
assignDeep,
|
||||
each,
|
||||
isObject,
|
||||
keys,
|
||||
isArray,
|
||||
hasOwnProperty,
|
||||
isFunction,
|
||||
isEmptyObject,
|
||||
} from 'support';
|
||||
import { DeepPartial, DeepReadonly } from 'typings';
|
||||
|
||||
const opsStringify = (value: any) =>
|
||||
@@ -80,6 +89,10 @@ export const getOptionsDiff = <T>(currOptions: T, newOptions: DeepPartial<T>): D
|
||||
|
||||
if (isObject(currOptionValue) && isObject(newOptionValue)) {
|
||||
assignDeep((diff[optionKey] = {}), getOptionsDiff(currOptionValue, newOptionValue));
|
||||
// delete empty nested objects
|
||||
if (isEmptyObject(diff[optionKey])) {
|
||||
delete diff[optionKey];
|
||||
}
|
||||
} else if (hasOwnProperty(newOptions, optionKey) && newOptionValue !== currOptionValue) {
|
||||
let isDiff = true;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getEnvironment } from 'environment';
|
||||
import { cancelInitialization } from 'initialization';
|
||||
import { addInstance, getInstance, removeInstance } from 'instances';
|
||||
import { createStructureSetup, createScrollbarsSetup } from 'setups';
|
||||
import { getPlugins, addPlugin, optionsValidationPluginName } from 'plugins';
|
||||
import { getPlugins, addPlugin, optionsValidationPluginName, PluginInstance } from 'plugins';
|
||||
import type { XY, TRBL } from 'support';
|
||||
import type { Options, ReadonlyOptions } from 'options';
|
||||
import type { Plugin, OptionsValidationPluginInstance } from 'plugins';
|
||||
@@ -109,6 +109,16 @@ export interface OverlayScrollbars {
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
const invokePluginInstance = (
|
||||
pluginInstance: PluginInstance,
|
||||
staticObj?: OverlayScrollbarsStatic | false | null | undefined | 0,
|
||||
instanceObj?: OverlayScrollbars | false | null | undefined | 0
|
||||
) => {
|
||||
if (isFunction(pluginInstance)) {
|
||||
pluginInstance(staticObj || undefined, instanceObj || undefined);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
target: InitializationTarget,
|
||||
@@ -122,13 +132,12 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
const potentialInstance = getInstance(instanceTarget);
|
||||
if (options && !potentialInstance) {
|
||||
let destroyed = false;
|
||||
const optionsValidationPlugin = plugins[
|
||||
optionsValidationPluginName
|
||||
] as OptionsValidationPluginInstance;
|
||||
const validateOptions = (newOptions?: DeepPartial<Options>) => {
|
||||
const opts = newOptions || {};
|
||||
const validateOptions = (newOptions: DeepPartial<Options>) => {
|
||||
const optionsValidationPlugin = getPlugins()[
|
||||
optionsValidationPluginName
|
||||
] as OptionsValidationPluginInstance;
|
||||
const validate = optionsValidationPlugin && optionsValidationPlugin._;
|
||||
return validate ? validate(opts, true) : opts;
|
||||
return validate ? validate(newOptions, true) : newOptions;
|
||||
};
|
||||
const currentOptions: ReadonlyOptions = assignDeep(
|
||||
{},
|
||||
@@ -166,7 +175,6 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
options(newOptions?: DeepPartial<Options>) {
|
||||
if (newOptions) {
|
||||
const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions));
|
||||
|
||||
if (!isEmptyObject(changedOptions)) {
|
||||
assignDeep(currentOptions, changedOptions);
|
||||
update(changedOptions);
|
||||
@@ -260,12 +268,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
updateScrollbars(changedOptions, force, updateHints);
|
||||
});
|
||||
|
||||
each(keys(plugins), (pluginName) => {
|
||||
const pluginInstance = plugins[pluginName];
|
||||
if (isFunction(pluginInstance)) {
|
||||
pluginInstance(OverlayScrollbars, instance);
|
||||
}
|
||||
});
|
||||
// valid inside plugins
|
||||
addInstance(instanceTarget, instance);
|
||||
|
||||
// init plugins
|
||||
each(keys(plugins), (pluginName) => invokePluginInstance(plugins[pluginName], 0, instance));
|
||||
|
||||
if (
|
||||
cancelInitialization(
|
||||
@@ -281,7 +288,6 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
structureState._appendElements();
|
||||
scrollbarsState._appendElements();
|
||||
|
||||
addInstance(instanceTarget, instance);
|
||||
triggerEvent('initialized', [instance]);
|
||||
|
||||
structureState._addOnUpdatedListener((updateHints, changedOptions, force) => {
|
||||
@@ -322,7 +328,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
return potentialInstance!;
|
||||
};
|
||||
|
||||
OverlayScrollbars.plugin = addPlugin;
|
||||
OverlayScrollbars.plugin = (plugins: Plugin | Plugin[]) => {
|
||||
each(addPlugin(plugins), (pluginInstance) =>
|
||||
invokePluginInstance(pluginInstance, OverlayScrollbars)
|
||||
);
|
||||
};
|
||||
OverlayScrollbars.valid = (osInstance: any): osInstance is OverlayScrollbars => {
|
||||
const hasElmsFn = osInstance && (osInstance as OverlayScrollbars).elements;
|
||||
const elements = isFunction(hasElmsFn) && hasElmsFn();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { each, isArray, keys } from 'support';
|
||||
import { each, isArray, keys, push } from 'support';
|
||||
import { OverlayScrollbars, OverlayScrollbarsStatic } from 'overlayscrollbars';
|
||||
|
||||
export type PluginInstance =
|
||||
| Record<string, unknown>
|
||||
| ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
| ((staticObj?: OverlayScrollbarsStatic, instanceObj?: OverlayScrollbars) => void);
|
||||
export type Plugin<T extends PluginInstance = PluginInstance> = {
|
||||
[pluginName: string]: T;
|
||||
};
|
||||
@@ -12,9 +12,14 @@ const pluginRegistry: Record<string, PluginInstance> = {};
|
||||
|
||||
export const getPlugins = () => pluginRegistry;
|
||||
|
||||
export const addPlugin = (addedPlugin: Plugin | Plugin[]): void => {
|
||||
export const addPlugin = (addedPlugin: Plugin | Plugin[]): Plugin[] => {
|
||||
const result: Plugin[] = [];
|
||||
each((isArray(addedPlugin) ? addedPlugin : [addedPlugin]) as Plugin[], (plugin) => {
|
||||
const pluginName = keys(plugin)[0];
|
||||
pluginRegistry[pluginName] = plugin[pluginName];
|
||||
// multiple "sub-plugins" per plugin object possible to support "static", "instanceObj" and "staticObj" sub-plugins per plugin
|
||||
const pluginNameKeys = keys(plugin);
|
||||
each(pluginNameKeys, (key) => {
|
||||
push(result, (pluginRegistry[key] = plugin[key]));
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ import type {
|
||||
} from 'initialization';
|
||||
|
||||
export type StructureSetupElements = [
|
||||
targetObj: StructureSetupElementsObj,
|
||||
elements: StructureSetupElementsObj,
|
||||
appendElements: () => void,
|
||||
destroy: () => void
|
||||
];
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('options', () => {
|
||||
b: 0,
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, options)).toEqual({});
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
@@ -74,6 +75,7 @@ describe('options', () => {
|
||||
},
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, options)).toEqual({});
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DeepPartial } from 'typings';
|
||||
import { defaultOptions, Options } from 'options';
|
||||
import { assignDeep } from 'support';
|
||||
import { optionsValidationPlugin } from 'plugins';
|
||||
import { OverlayScrollbars as originalOverlayScrollbars } from '../../src/overlayscrollbars';
|
||||
|
||||
const bodyElm = document.body;
|
||||
@@ -155,26 +156,144 @@ describe('overlayscrollbars', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('options', () => {
|
||||
const customOptions: DeepPartial<Options> = {
|
||||
paddingAbsolute: !defaultOptions.paddingAbsolute,
|
||||
overflow: { x: 'hidden' },
|
||||
};
|
||||
const osInstance = OverlayScrollbars(div, {});
|
||||
expect(osInstance.options()).not.toBe(defaultOptions);
|
||||
expect(osInstance.options()).toEqual(defaultOptions);
|
||||
OverlayScrollbars(div)!.destroy();
|
||||
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(OverlayScrollbars(div, customOptions).options()).toEqual(
|
||||
assignDeep({}, defaultOptions, customOptions)
|
||||
);
|
||||
OverlayScrollbars(div)!.destroy();
|
||||
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);
|
||||
|
||||
const osInstance2 = OverlayScrollbars(div, {});
|
||||
expect(osInstance2.options(customOptions)).toEqual(
|
||||
assignDeep({}, defaultOptions, customOptions)
|
||||
);
|
||||
expect(osInstance2.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
|
||||
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: DeepPartial<Options> = {
|
||||
paddingAbsolute: !defaultOptions.paddingAbsolute,
|
||||
overflow: { x: 'hidden' },
|
||||
};
|
||||
|
||||
const osInstance = OverlayScrollbars(div, customOptions);
|
||||
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
|
||||
});
|
||||
|
||||
test('changed options', () => {
|
||||
const customOptions: DeepPartial<Options> = {
|
||||
paddingAbsolute: !defaultOptions.paddingAbsolute,
|
||||
overflow: { x: 'hidden' },
|
||||
};
|
||||
|
||||
const osInstance = OverlayScrollbars(div, {});
|
||||
expect(osInstance.options(customOptions)).toEqual(
|
||||
assignDeep({}, defaultOptions, customOptions)
|
||||
);
|
||||
expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions));
|
||||
osInstance.destroy();
|
||||
});
|
||||
|
||||
test('unchanged wont trigger update', () => {
|
||||
const updated = jest.fn();
|
||||
const initialOpts: DeepPartial<Options> = {
|
||||
paddingAbsolute: true,
|
||||
overflow: {
|
||||
y: 'hidden',
|
||||
},
|
||||
};
|
||||
const osInstance4 = OverlayScrollbars(div, initialOpts, {
|
||||
updated,
|
||||
});
|
||||
expect(updated).toHaveBeenCalledTimes(1);
|
||||
osInstance4.options(initialOpts);
|
||||
expect(updated).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('on', () => {
|
||||
@@ -296,59 +415,6 @@ describe('overlayscrollbars', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('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('update', () => {
|
||||
const osInstance = OverlayScrollbars(div, {});
|
||||
expect(osInstance.update()).toBe(false);
|
||||
@@ -405,4 +471,64 @@ describe('overlayscrollbars', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,19 +9,24 @@ describe('plugins', () => {
|
||||
test('addPlugin single', () => {
|
||||
const myPlugin = {};
|
||||
const myPlugin2 = {};
|
||||
addPlugin({
|
||||
const addedPlugins = addPlugin({
|
||||
myPlugin,
|
||||
myPlugin2,
|
||||
});
|
||||
expect(addedPlugins.length).toBe(2);
|
||||
expect(addedPlugins[0]).toBe(myPlugin);
|
||||
expect(addedPlugins[1]).toBe(myPlugin2);
|
||||
|
||||
const plugins = getPlugins();
|
||||
expect(plugins.myPlugin).toBe(myPlugin);
|
||||
expect(plugins.myPlugin2).toBe(undefined); // one plugin per object
|
||||
// multiple "sub-plugins" per plugin object possible to support "static", "instanceObj" and "staticObj" sub-plugins per plugin
|
||||
expect(plugins.myPlugin2).toBe(myPlugin2);
|
||||
});
|
||||
|
||||
test('addPlugin multiple', () => {
|
||||
const myPlugin = {};
|
||||
const myPlugin2 = {};
|
||||
addPlugin([
|
||||
const addedPlugins = addPlugin([
|
||||
{
|
||||
myPlugin,
|
||||
},
|
||||
@@ -29,6 +34,11 @@ describe('plugins', () => {
|
||||
myPlugin2,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(addedPlugins.length).toBe(2);
|
||||
expect(addedPlugins[0]).toBe(myPlugin);
|
||||
expect(addedPlugins[1]).toBe(myPlugin2);
|
||||
|
||||
const plugins = getPlugins();
|
||||
expect(plugins.myPlugin).toBe(myPlugin);
|
||||
expect(plugins.myPlugin2).toBe(myPlugin2);
|
||||
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
import {
|
||||
createScrollbarsSetupElements,
|
||||
ScrollbarsSetupElement,
|
||||
ScrollbarsSetupElementsObj,
|
||||
ScrollbarStructure,
|
||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
|
||||
import {
|
||||
createStructureSetupElements,
|
||||
StructureSetupElementsObj,
|
||||
} from 'setups/structureSetup/structureSetup.elements';
|
||||
import {
|
||||
classNameScrollbar,
|
||||
classNameScrollbarHorizontal,
|
||||
classNameScrollbarVertical,
|
||||
classNameScrollbarTrack,
|
||||
classNameScrollbarHandle,
|
||||
classNamesScrollbarTransitionless,
|
||||
} from 'classnames';
|
||||
import type { InitializationTarget } from 'initialization';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('support/compatibility/apis', () => {
|
||||
const originalModule = jest.requireActual('support/compatibility/apis');
|
||||
return {
|
||||
...originalModule,
|
||||
// @ts-ignore
|
||||
setT: jest.fn().mockImplementation((...args) => setTimeout(...args)),
|
||||
clearT: jest.fn().mockImplementation((...args) => clearTimeout(...args)),
|
||||
};
|
||||
});
|
||||
|
||||
const getTarget = () => document.body.firstElementChild as HTMLElement;
|
||||
|
||||
const domSnapshot = (element?: HTMLElement) => {
|
||||
const getResult = () => (element ? element.innerHTML : document.documentElement.outerHTML);
|
||||
return [getResult(), () => getResult()] as [string, () => string];
|
||||
};
|
||||
|
||||
const createStructureSetupElementsProxy = (target: InitializationTarget) => {
|
||||
const [structureElements, , destroyStructureElements] = createStructureSetupElements(target);
|
||||
const [elements, appendElements, destroy] = createScrollbarsSetupElements(
|
||||
target,
|
||||
structureElements,
|
||||
() => () => {}
|
||||
);
|
||||
|
||||
appendElements();
|
||||
|
||||
return [
|
||||
elements,
|
||||
() => {
|
||||
destroyStructureElements();
|
||||
destroy();
|
||||
},
|
||||
structureElements,
|
||||
] as [
|
||||
elements: ScrollbarsSetupElementsObj,
|
||||
destroy: () => void,
|
||||
structureElements: StructureSetupElementsObj
|
||||
];
|
||||
};
|
||||
|
||||
const assertCorrectDOMStructure = (
|
||||
elements: ScrollbarsSetupElementsObj,
|
||||
target: HTMLElement | ((isHorizontal?: boolean) => HTMLElement),
|
||||
getTargetStructure?: (
|
||||
structures: ScrollbarStructure[],
|
||||
isHorizontal?: boolean
|
||||
) => ScrollbarStructure
|
||||
) => {
|
||||
const getStructure = (isHorizontal?: boolean) =>
|
||||
isHorizontal
|
||||
? elements._horizontal._scrollbarStructures
|
||||
: elements._vertical._scrollbarStructures;
|
||||
const assertScrollbarStructure = (isHorizontal?: boolean) => {
|
||||
const resolvedTarget = typeof target === 'function' ? target(isHorizontal) : target;
|
||||
const domScrollbar = resolvedTarget.querySelector(
|
||||
`.${isHorizontal ? classNameScrollbarHorizontal : classNameScrollbarVertical}`
|
||||
) as HTMLElement;
|
||||
const structures = getStructure(isHorizontal);
|
||||
|
||||
expect(structures.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const targetStructure = getTargetStructure?.(structures, isHorizontal) || structures[0];
|
||||
const isMainStructure = targetStructure === structures[0];
|
||||
const { _scrollbar, _track, _handle } = targetStructure;
|
||||
|
||||
// classnames
|
||||
expect(domScrollbar).toEqual(expect.any(HTMLElement));
|
||||
expect(domScrollbar.classList.contains(classNameScrollbar)).toBe(true);
|
||||
expect(_track.classList.contains(classNameScrollbarTrack)).toBe(true);
|
||||
expect(_handle.classList.contains(classNameScrollbarHandle)).toBe(true);
|
||||
expect(_track.classList.length).toBe(1);
|
||||
expect(_handle.classList.length).toBe(1);
|
||||
if (isMainStructure) {
|
||||
expect(domScrollbar.classList.contains(classNamesScrollbarTransitionless)).toBe(true);
|
||||
}
|
||||
|
||||
// structure
|
||||
expect(_scrollbar).toBe(domScrollbar);
|
||||
expect(_track.closest(`.${classNameScrollbar}`)).toBe(_scrollbar);
|
||||
expect(_handle.closest(`.${classNameScrollbarTrack}`)).toBe(_track);
|
||||
};
|
||||
|
||||
assertScrollbarStructure(true);
|
||||
assertScrollbarStructure();
|
||||
};
|
||||
|
||||
describe('scrollbarsSetup.elements', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '<div></div>';
|
||||
});
|
||||
|
||||
[false, true].forEach((targetIsObj) => {
|
||||
describe(`as target ${targetIsObj ? 'object' : 'element'}`, () => {
|
||||
test('initialization and destruction', () => {
|
||||
const target = getTarget();
|
||||
const [beforeInitSnapshot, beforeInitSnapshotFn] = domSnapshot();
|
||||
const [elements, destroy] = createStructureSetupElementsProxy(
|
||||
targetIsObj ? { target } : target
|
||||
);
|
||||
|
||||
assertCorrectDOMStructure(elements, target);
|
||||
|
||||
destroy();
|
||||
|
||||
expect(beforeInitSnapshot).toBe(beforeInitSnapshotFn());
|
||||
});
|
||||
|
||||
test('cloning', () => {
|
||||
const target = getTarget();
|
||||
const [beforeInitSnapshot, beforeInitSnapshotFn] = domSnapshot();
|
||||
const [elements, destroy] = createStructureSetupElementsProxy(
|
||||
targetIsObj ? { target } : target
|
||||
);
|
||||
const clonedHorizontalSlot = document.createElement('div');
|
||||
const clonedVerticalSlot = document.createElement('div');
|
||||
|
||||
const [beforeCloneHorizontalSnapshot, beforeCloneHorizontalSnapshotFn] =
|
||||
domSnapshot(clonedHorizontalSlot);
|
||||
const [beforeCloneVerticalSnapshot, beforeCloneVerticalSnapshotFn] =
|
||||
domSnapshot(clonedVerticalSlot);
|
||||
|
||||
const clonedHorizontal = elements._horizontal._clone();
|
||||
const clonedVertical = elements._vertical._clone();
|
||||
|
||||
clonedHorizontalSlot.append(clonedHorizontal._scrollbar);
|
||||
clonedVerticalSlot.append(clonedVertical._scrollbar);
|
||||
|
||||
target.append(clonedHorizontalSlot);
|
||||
target.append(clonedVerticalSlot);
|
||||
|
||||
assertCorrectDOMStructure(elements, target);
|
||||
assertCorrectDOMStructure(
|
||||
elements,
|
||||
(isHorizontal) => (isHorizontal ? clonedHorizontalSlot : clonedVerticalSlot),
|
||||
(structures, isHorizontal) => {
|
||||
const clonedStructure = isHorizontal ? clonedHorizontal : clonedVertical;
|
||||
return structures.find(
|
||||
(structure) => structure._scrollbar === clonedStructure._scrollbar
|
||||
)!;
|
||||
}
|
||||
);
|
||||
|
||||
destroy();
|
||||
|
||||
// destroy cleans up clones as well
|
||||
expect(beforeCloneHorizontalSnapshot).toBe(beforeCloneHorizontalSnapshotFn());
|
||||
expect(beforeCloneVerticalSnapshot).toBe(beforeCloneVerticalSnapshotFn());
|
||||
|
||||
clonedHorizontalSlot.remove();
|
||||
clonedVerticalSlot.remove();
|
||||
|
||||
expect(beforeInitSnapshot).toBe(beforeInitSnapshotFn());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addRemoveClass', () => {
|
||||
test('add & remove classes to both axis', () => {
|
||||
const target = getTarget();
|
||||
const [elements] = createStructureSetupElementsProxy(target);
|
||||
|
||||
const horizontalStructures = elements._horizontal._scrollbarStructures;
|
||||
const verticalStructures = elements._vertical._scrollbarStructures;
|
||||
const className = 'aksjhdkasjd';
|
||||
|
||||
// clones before have the class
|
||||
elements._horizontal._clone();
|
||||
elements._vertical._clone();
|
||||
|
||||
elements._scrollbarsAddRemoveClass(className, true);
|
||||
|
||||
// clones after do not have the class
|
||||
elements._horizontal._clone();
|
||||
elements._vertical._clone();
|
||||
|
||||
horizontalStructures.forEach(({ _scrollbar }, i, arr) => {
|
||||
if (i === arr.length - 1) {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(false);
|
||||
} else {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(true);
|
||||
}
|
||||
});
|
||||
verticalStructures.forEach(({ _scrollbar }, i, arr) => {
|
||||
if (i === arr.length - 1) {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(false);
|
||||
} else {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
elements._scrollbarsAddRemoveClass(className);
|
||||
|
||||
horizontalStructures.forEach(({ _scrollbar }) => {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(false);
|
||||
});
|
||||
verticalStructures.forEach(({ _scrollbar }) => {
|
||||
expect(_scrollbar.classList.contains(className)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('add & remove classes to individual axis', () => {
|
||||
const target = getTarget();
|
||||
const [elements] = createStructureSetupElementsProxy(target);
|
||||
|
||||
const horizontalStructures = elements._horizontal._scrollbarStructures;
|
||||
const verticalStructures = elements._vertical._scrollbarStructures;
|
||||
const classNameHorizontal = 'hhhhhhhhh12sdsdf';
|
||||
const classNameVertical = 'vvvvvvv12sdsdf';
|
||||
|
||||
// clones before have the class
|
||||
elements._horizontal._clone();
|
||||
elements._vertical._clone();
|
||||
|
||||
elements._scrollbarsAddRemoveClass(classNameHorizontal, true, true);
|
||||
elements._scrollbarsAddRemoveClass(classNameVertical, true, false);
|
||||
|
||||
// clones after do not have the class
|
||||
elements._horizontal._clone();
|
||||
elements._vertical._clone();
|
||||
|
||||
horizontalStructures.forEach(({ _scrollbar }, i, arr) => {
|
||||
if (i === arr.length - 1) {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(false);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(false);
|
||||
} else {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(true);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(false);
|
||||
}
|
||||
});
|
||||
verticalStructures.forEach(({ _scrollbar }, i, arr) => {
|
||||
if (i === arr.length - 1) {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(false);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(false);
|
||||
} else {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(false);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
elements._scrollbarsAddRemoveClass(classNameHorizontal, false, true);
|
||||
elements._scrollbarsAddRemoveClass(classNameVertical, false, false);
|
||||
|
||||
horizontalStructures.forEach(({ _scrollbar }) => {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(false);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(false);
|
||||
});
|
||||
verticalStructures.forEach(({ _scrollbar }) => {
|
||||
expect(_scrollbar.classList.contains(classNameHorizontal)).toBe(false);
|
||||
expect(_scrollbar.classList.contains(classNameVertical)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('initialization and destruction in custom slot', () => {
|
||||
const target = getTarget();
|
||||
const slotFn = jest.fn(() => document.body);
|
||||
const [beforeInitSnapshot, beforeInitSnapshotFn] = domSnapshot();
|
||||
const [elements, destroy, structureElements] = createStructureSetupElementsProxy({
|
||||
target,
|
||||
scrollbars: {
|
||||
slot: slotFn,
|
||||
},
|
||||
});
|
||||
|
||||
expect(slotFn).toHaveBeenCalledTimes(1);
|
||||
expect(slotFn).toHaveBeenCalledWith(
|
||||
structureElements._target,
|
||||
structureElements._host,
|
||||
structureElements._viewport
|
||||
);
|
||||
|
||||
assertCorrectDOMStructure(elements, document.body);
|
||||
|
||||
destroy();
|
||||
|
||||
expect(beforeInitSnapshot).toBe(beforeInitSnapshotFn());
|
||||
});
|
||||
|
||||
test('handleStyle', () => {
|
||||
const target = getTarget();
|
||||
const [elements] = createStructureSetupElementsProxy(target);
|
||||
const testHandleStyle = (scrollbarSetupElement: ScrollbarsSetupElement) => {
|
||||
// before cloned elements have the style
|
||||
scrollbarSetupElement._clone();
|
||||
|
||||
scrollbarSetupElement._handleStyle((structure) => {
|
||||
const { _scrollbar } = structure;
|
||||
return [_scrollbar, { width: '0px' }];
|
||||
});
|
||||
scrollbarSetupElement._handleStyle((structure) => {
|
||||
const { _track } = structure;
|
||||
return [_track, { width: '1px' }];
|
||||
});
|
||||
scrollbarSetupElement._handleStyle((structure) => {
|
||||
const { _handle } = structure;
|
||||
return [_handle, { width: '2px' }];
|
||||
});
|
||||
|
||||
// before cloned elements don not have the style
|
||||
scrollbarSetupElement._clone();
|
||||
|
||||
scrollbarSetupElement._scrollbarStructures.forEach(
|
||||
({ _scrollbar, _track, _handle }, i, arr) => {
|
||||
if (i === arr.length - 1) {
|
||||
expect(_scrollbar.style.width).not.toBe('0px');
|
||||
expect(_track.style.width).not.toBe('1px');
|
||||
expect(_handle.style.width).not.toBe('2px');
|
||||
} else {
|
||||
expect(_scrollbar.style.width).toBe('0px');
|
||||
expect(_track.style.width).toBe('1px');
|
||||
expect(_handle.style.width).toBe('2px');
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
testHandleStyle(elements._horizontal);
|
||||
testHandleStyle(elements._vertical);
|
||||
});
|
||||
|
||||
test('removes transitionless class', () => {
|
||||
const target = getTarget();
|
||||
const [elements] = createStructureSetupElementsProxy(target);
|
||||
const testHasTransitionlessClass = (
|
||||
setupElement: ScrollbarsSetupElement,
|
||||
expected: boolean
|
||||
) => {
|
||||
const { _scrollbar } = setupElement._scrollbarStructures[0];
|
||||
expect(_scrollbar.classList.contains(classNamesScrollbarTransitionless)).toBe(expected);
|
||||
};
|
||||
|
||||
testHasTransitionlessClass(elements._horizontal, true);
|
||||
testHasTransitionlessClass(elements._vertical, true);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
testHasTransitionlessClass(elements._horizontal, false);
|
||||
testHasTransitionlessClass(elements._vertical, false);
|
||||
});
|
||||
});
|
||||
+101
-43
@@ -1,6 +1,11 @@
|
||||
import { hasClass, is, isFunction, isHTMLElement } from 'support';
|
||||
import { dataAttributeHost } from 'classnames';
|
||||
import { InternalEnvironment } from 'environment';
|
||||
import {
|
||||
dataAttributeHost,
|
||||
classNamePadding,
|
||||
classNameViewport,
|
||||
classNameContent,
|
||||
} from 'classnames';
|
||||
import { getEnvironment, InternalEnvironment } from 'environment';
|
||||
import {
|
||||
createStructureSetupElements,
|
||||
StructureSetupElementsObj,
|
||||
@@ -12,9 +17,8 @@ import type {
|
||||
InitializationTargetObject,
|
||||
} from 'initialization';
|
||||
|
||||
const mockGetEnvironment = jest.fn();
|
||||
jest.mock('environment', () => ({
|
||||
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
|
||||
getEnvironment: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('support/compatibility/apis', () => {
|
||||
@@ -81,10 +85,10 @@ const clearBody = () => {
|
||||
|
||||
const getElements = (targetType: TargetType) => {
|
||||
const target = getTarget(targetType);
|
||||
const host = document.querySelector('[data-overlayscrollbars]')!;
|
||||
const padding = document.querySelector('.os-padding')!;
|
||||
const viewport = document.querySelector('.os-viewport')!;
|
||||
const content = document.querySelector('.os-content')!;
|
||||
const host = document.querySelector(`[${dataAttributeHost}]`)!;
|
||||
const padding = document.querySelector(`.${classNamePadding}`)!;
|
||||
const viewport = document.querySelector(`.${classNameViewport}`)!;
|
||||
const content = document.querySelector(`.${classNameContent}`)!;
|
||||
|
||||
return {
|
||||
target,
|
||||
@@ -133,7 +137,9 @@ const assertCorrectDOMStructure = (targetType: TargetType, viewportIsTarget: boo
|
||||
}
|
||||
};
|
||||
|
||||
const createStructureSetupProxy = (target: InitializationTarget): StructureSetupElementsProxy => {
|
||||
const createStructureSetupElementsProxy = (
|
||||
target: InitializationTarget
|
||||
): StructureSetupElementsProxy => {
|
||||
const [elements, appendElements, destroy] = createStructureSetupElements(target);
|
||||
appendElements();
|
||||
return {
|
||||
@@ -422,9 +428,15 @@ const envInitStrategyViewportIsTarget = {
|
||||
},
|
||||
};
|
||||
|
||||
describe('structureSetup', () => {
|
||||
describe('structureSetup.elements', () => {
|
||||
afterEach(() => clearBody());
|
||||
|
||||
beforeEach(() => {
|
||||
(getEnvironment as jest.Mock).mockImplementation(() =>
|
||||
jest.requireActual('environment').getEnvironment()
|
||||
);
|
||||
});
|
||||
|
||||
[
|
||||
envDefault,
|
||||
envNativeScrollbarStyling,
|
||||
@@ -436,8 +448,8 @@ describe('structureSetup', () => {
|
||||
].forEach((envWithName) => {
|
||||
const { env: currEnv, name } = envWithName;
|
||||
describe(`Environment: ${name}`, () => {
|
||||
beforeAll(() => {
|
||||
mockGetEnvironment.mockImplementation(() => currEnv);
|
||||
beforeEach(() => {
|
||||
(getEnvironment as jest.Mock).mockImplementation(() => currEnv);
|
||||
});
|
||||
|
||||
(['element', 'textarea', 'body'] as TargetType[]).forEach((targetType) => {
|
||||
@@ -447,7 +459,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy(getTarget(targetType)),
|
||||
createStructureSetupElementsProxy(getTarget(targetType)),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(targetType, elements._viewportIsTarget);
|
||||
@@ -458,7 +470,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({ target: getTarget(targetType) }),
|
||||
createStructureSetupElementsProxy({ target: getTarget(targetType) }),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(targetType, elements._viewportIsTarget);
|
||||
@@ -476,7 +488,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -497,7 +509,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: () => document.querySelector<HTMLElement>('#host'),
|
||||
@@ -518,7 +530,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -541,7 +553,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -564,7 +576,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: () => document.querySelector<HTMLElement>('#host'),
|
||||
@@ -586,7 +598,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -608,7 +620,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -628,7 +640,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
padding: false,
|
||||
@@ -644,7 +656,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
content: () => false,
|
||||
@@ -662,7 +674,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
padding: () => true,
|
||||
@@ -678,7 +690,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
content: true,
|
||||
@@ -696,7 +708,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
padding: false,
|
||||
@@ -715,7 +727,7 @@ describe('structureSetup', () => {
|
||||
const snapshot = fillBody(targetType);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
padding: true,
|
||||
@@ -738,7 +750,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -761,7 +773,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -784,7 +796,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -807,7 +819,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -830,7 +842,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -852,7 +864,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -874,7 +886,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -896,7 +908,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -918,7 +930,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -941,7 +953,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -964,7 +976,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -986,7 +998,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: () => document.querySelector<HTMLElement>('#host'),
|
||||
@@ -1008,7 +1020,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -1030,7 +1042,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -1052,7 +1064,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: document.querySelector<HTMLElement>('#host'),
|
||||
@@ -1075,7 +1087,7 @@ describe('structureSetup', () => {
|
||||
);
|
||||
const [elements, destroy] = assertCorrectSetupElements(
|
||||
targetType,
|
||||
createStructureSetupProxy({
|
||||
createStructureSetupElementsProxy({
|
||||
target: getTarget(targetType),
|
||||
elements: {
|
||||
host: () => document.querySelector<HTMLElement>('#host'),
|
||||
@@ -1095,4 +1107,50 @@ describe('structureSetup', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('focus', () => {
|
||||
describe('shift tabindex to viewport', () => {
|
||||
test('with pointerdown on body', () => {
|
||||
const { elements } = createStructureSetupElementsProxy(document.body);
|
||||
expect(elements._viewport.getAttribute('tabindex')).toBe('-1');
|
||||
expect(document.activeElement).toBe(elements._viewport);
|
||||
|
||||
elements._documentElm.dispatchEvent(new Event('pointerdown'));
|
||||
|
||||
expect(elements._viewport.getAttribute('tabindex')).toBe(null);
|
||||
});
|
||||
|
||||
test('with keydown on element', () => {
|
||||
document.body.innerHTML = '<div tabindex="123"></div>';
|
||||
const target = document.body.firstElementChild as HTMLElement;
|
||||
target.focus();
|
||||
|
||||
const { elements } = createStructureSetupElementsProxy(target);
|
||||
expect(elements._viewport.getAttribute('tabindex')).toBe('-1');
|
||||
expect(document.activeElement).toBe(elements._viewport);
|
||||
|
||||
elements._documentElm.dispatchEvent(new Event('keydown'));
|
||||
|
||||
expect(elements._viewport.getAttribute('tabindex')).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
test('shift tabindex back to original activeElement', () => {
|
||||
document.body.innerHTML = '<input type="text" value="hi"></input>';
|
||||
const input = document.querySelector('input') as HTMLInputElement;
|
||||
const target = document.body;
|
||||
|
||||
input.focus();
|
||||
const preInitFocus = document.activeElement;
|
||||
|
||||
const { elements } = createStructureSetupElementsProxy(target);
|
||||
|
||||
expect(preInitFocus).toBe(document.activeElement);
|
||||
|
||||
elements._documentElm.dispatchEvent(new Event('pointerdown'));
|
||||
elements._documentElm.dispatchEvent(new Event('keydown'));
|
||||
|
||||
expect(preInitFocus).toBe(document.activeElement);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user