mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-08 12:13:09 +03:00
improve event listener code and tests
This commit is contained in:
@@ -31,9 +31,9 @@ import { Options, defaultOptions } from 'options';
|
||||
import { PartialOptions } from 'typings';
|
||||
import { InitializationStrategy } from 'initialization';
|
||||
|
||||
export interface EnvironmentListenersNameArgsMap {
|
||||
_: undefined;
|
||||
}
|
||||
type EnvironmentEventMap = {
|
||||
_: [];
|
||||
};
|
||||
|
||||
export interface InternalEnvironment {
|
||||
readonly _nativeScrollbarSize: XY;
|
||||
@@ -44,7 +44,7 @@ export interface InternalEnvironment {
|
||||
readonly _cssCustomProperties: boolean;
|
||||
readonly _defaultInitializationStrategy: InitializationStrategy;
|
||||
readonly _defaultDefaultOptions: Options;
|
||||
_addListener(listener: EventListener<EnvironmentListenersNameArgsMap, '_'>): () => void;
|
||||
_addListener(listener: EventListener<EnvironmentEventMap, '_'>): () => void;
|
||||
_getInitializationStrategy(): InitializationStrategy;
|
||||
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
|
||||
_getDefaultOptions(): Options;
|
||||
@@ -152,7 +152,7 @@ const createEnvironment = (): InternalEnvironment => {
|
||||
const envDOM = createDOM(`<div class="${classNameEnvironment}"><div></div></div>`);
|
||||
const envElm = envDOM[0] as HTMLElement;
|
||||
const envChildElm = envElm.firstChild as HTMLElement;
|
||||
const [addEvent, , triggerEvent] = createEventListenerHub<EnvironmentListenersNameArgsMap>();
|
||||
const [addEvent, , triggerEvent] = createEventListenerHub<EnvironmentEventMap>();
|
||||
const [updateNativeScrollbarSizeCache, getNativeScrollbarSizeCache] = createCache({
|
||||
_initialValue: getNativeScrollbarSize(body, envElm, envChildElm),
|
||||
_equal: equalXY,
|
||||
|
||||
@@ -99,12 +99,12 @@ export interface OnUpdatedEventListenerArgs {
|
||||
force: boolean;
|
||||
}
|
||||
|
||||
export interface EventListenerMap {
|
||||
initialized: undefined;
|
||||
initializationWithdrawn: undefined;
|
||||
updated: OnUpdatedEventListenerArgs;
|
||||
destroyed: undefined;
|
||||
}
|
||||
export type EventListenerMap = {
|
||||
initialized: [];
|
||||
initializationWithdrawn: [];
|
||||
updated: [OnUpdatedEventListenerArgs];
|
||||
destroyed: [];
|
||||
};
|
||||
|
||||
export type InitialEventListeners = GeneralInitialEventListeners<EventListenerMap>;
|
||||
|
||||
@@ -128,8 +128,8 @@ export interface OverlayScrollbars {
|
||||
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>): () => void;
|
||||
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>[]): () => void;
|
||||
|
||||
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener<Name>): void;
|
||||
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener<Name>[]): void;
|
||||
off<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>): void;
|
||||
off<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>[]): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,19 +205,21 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
_hostMutation,
|
||||
} = updateHints;
|
||||
|
||||
triggerEvent('updated', {
|
||||
updateHints: {
|
||||
sizeChanged: _sizeChanged,
|
||||
directionChanged: _directionChanged,
|
||||
heightIntrinsicChanged: _heightIntrinsicChanged,
|
||||
overflowAmountChanged: _overflowAmountChanged,
|
||||
overflowStyleChanged: _overflowStyleChanged,
|
||||
contentMutation: _contentMutation,
|
||||
hostMutation: _hostMutation,
|
||||
triggerEvent('updated', [
|
||||
{
|
||||
updateHints: {
|
||||
sizeChanged: _sizeChanged,
|
||||
directionChanged: _directionChanged,
|
||||
heightIntrinsicChanged: _heightIntrinsicChanged,
|
||||
overflowAmountChanged: _overflowAmountChanged,
|
||||
overflowStyleChanged: _overflowStyleChanged,
|
||||
contentMutation: _contentMutation,
|
||||
hostMutation: _hostMutation,
|
||||
},
|
||||
changedOptions,
|
||||
force,
|
||||
},
|
||||
changedOptions,
|
||||
force,
|
||||
});
|
||||
]);
|
||||
});
|
||||
|
||||
const instance: OverlayScrollbars = {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { isArray } from 'support/utils/types';
|
||||
import { keys } from 'support/utils/object';
|
||||
import { each, from } from 'support/utils/array';
|
||||
import { each, from, isEmptyArray } from 'support/utils/array';
|
||||
|
||||
export type EventListener<
|
||||
EventMap extends Record<string, any>,
|
||||
EventMap extends Record<string, any[]>,
|
||||
Name extends keyof EventMap = keyof EventMap
|
||||
> = (...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]) => void;
|
||||
> = (...args: EventMap[Name]) => void;
|
||||
|
||||
export type InitialEventListeners<EventMap extends Record<string, any>> = {
|
||||
export type InitialEventListeners<EventMap extends Record<string, any[]>> = {
|
||||
[K in keyof EventMap]?: EventListener<EventMap> | EventListener<EventMap>[];
|
||||
};
|
||||
|
||||
const manageListener = <EventMap extends Record<string, any>>(
|
||||
const manageListener = <EventMap extends Record<string, any[]>>(
|
||||
callback: (listener?: EventListener<EventMap>) => void,
|
||||
listener?: EventListener<EventMap> | EventListener<EventMap>[]
|
||||
) => {
|
||||
each(isArray(listener) ? listener : [listener], callback);
|
||||
};
|
||||
|
||||
export const createEventListenerHub = <EventMap extends Record<string, any>>(
|
||||
export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
|
||||
initialEventListeners?: InitialEventListeners<EventMap>
|
||||
) => {
|
||||
type EventListener<Name extends keyof EventMap = keyof EventMap> = (
|
||||
...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]
|
||||
...args: EventMap[Name]
|
||||
) => void;
|
||||
|
||||
const events = new Map<keyof EventMap, Set<EventListener>>();
|
||||
@@ -76,15 +76,12 @@ export const createEventListenerHub = <EventMap extends Record<string, any>>(
|
||||
return removeEvent.bind(0, name as any, listener as any);
|
||||
}
|
||||
|
||||
function triggerEvent<Name extends keyof EventMap>(
|
||||
name: Name,
|
||||
...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]
|
||||
): void {
|
||||
function triggerEvent<Name extends keyof EventMap>(name: Name, args?: EventMap[Name]): void {
|
||||
const eventSet = events.get(name);
|
||||
|
||||
each(from(eventSet), (event) => {
|
||||
if (args) {
|
||||
(event as (args: EventMap[keyof EventMap]) => void)(args as any);
|
||||
if (args && !isEmptyArray(args)) {
|
||||
(event as (...args: EventMap[keyof EventMap]) => void).apply(0, args as any);
|
||||
} else {
|
||||
(event as () => void)();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { createEventListenerHub } from 'support/eventListeners';
|
||||
|
||||
type EventMap = {
|
||||
onBoolean: [boolean, string];
|
||||
onUndefined: [];
|
||||
};
|
||||
|
||||
describe('eventListeners', () => {
|
||||
describe('createEventListenerHub', () => {
|
||||
test('initialization', () => {
|
||||
const onBooleanA = jest.fn();
|
||||
const onBooleanB = jest.fn();
|
||||
const onUndefined = jest.fn();
|
||||
const [, , triggerEvent] = createEventListenerHub<EventMap>({
|
||||
onBoolean: [onBooleanA, onBooleanB],
|
||||
onUndefined,
|
||||
});
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(1);
|
||||
expect(onBooleanA).toHaveBeenLastCalledWith(true, 'hi');
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(1);
|
||||
expect(onBooleanB).toHaveBeenLastCalledWith(true, 'hi');
|
||||
|
||||
expect(onUndefined).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefined).toHaveBeenLastCalledWith();
|
||||
});
|
||||
|
||||
test('addEvent', () => {
|
||||
const onBooleanA = jest.fn();
|
||||
const onBooleanB = jest.fn();
|
||||
const onUndefinedA = jest.fn();
|
||||
const onUndefinedB = jest.fn();
|
||||
const [addEvent, , triggerEvent] = createEventListenerHub<EventMap>();
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
addEvent('onBoolean', onBooleanA);
|
||||
addEvent('onUndefined', [onUndefinedA, onUndefinedB]);
|
||||
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(1);
|
||||
expect(onBooleanA).toHaveBeenLastCalledWith(true, 'hi');
|
||||
expect(onUndefinedA).not.toHaveBeenCalled();
|
||||
expect(onUndefinedB).not.toHaveBeenCalled();
|
||||
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefinedA).toHaveBeenLastCalledWith();
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefinedB).toHaveBeenLastCalledWith();
|
||||
|
||||
addEvent('onBoolean', onBooleanB);
|
||||
|
||||
triggerEvent('onBoolean', [false, 'hi2']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(2);
|
||||
expect(onBooleanA).toHaveBeenLastCalledWith(false, 'hi2');
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(1);
|
||||
expect(onBooleanB).toHaveBeenLastCalledWith(false, 'hi2');
|
||||
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(2);
|
||||
expect(onUndefinedA).toHaveBeenLastCalledWith();
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(2);
|
||||
expect(onUndefinedB).toHaveBeenLastCalledWith();
|
||||
|
||||
const something = jest.fn();
|
||||
// @ts-ignore
|
||||
addEvent('something', something);
|
||||
// @ts-ignore
|
||||
triggerEvent('something');
|
||||
expect(something).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('removeEvent', () => {
|
||||
const onBooleanA = jest.fn();
|
||||
const onBooleanB = jest.fn();
|
||||
const onUndefinedA = jest.fn();
|
||||
const onUndefinedB = jest.fn();
|
||||
const [addEvent, removeEvent, triggerEvent] = createEventListenerHub<EventMap>({
|
||||
onBoolean: [onBooleanA, onBooleanB],
|
||||
});
|
||||
const removeUndefined = addEvent('onUndefined', [onUndefinedA, onUndefinedB]);
|
||||
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(1);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(1);
|
||||
|
||||
removeUndefined();
|
||||
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(2);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(2);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(1);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(1);
|
||||
|
||||
removeEvent('onBoolean', onBooleanA);
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(2);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(3);
|
||||
|
||||
removeEvent('onBoolean', onBooleanB);
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(2);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(3);
|
||||
|
||||
addEvent('onBoolean', [onBooleanA, onBooleanB]);
|
||||
addEvent('onUndefined', [onUndefinedA, onUndefinedB]);
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(3);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(4);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(2);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(2);
|
||||
|
||||
removeEvent('onBoolean');
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(3);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(4);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(3);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(3);
|
||||
|
||||
addEvent('onBoolean', [onBooleanA, onBooleanB]);
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(4);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(5);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(4);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(4);
|
||||
|
||||
removeEvent();
|
||||
triggerEvent('onBoolean', [true, 'hi']);
|
||||
triggerEvent('onUndefined');
|
||||
|
||||
expect(onBooleanA).toHaveBeenCalledTimes(4);
|
||||
expect(onBooleanB).toHaveBeenCalledTimes(5);
|
||||
expect(onUndefinedA).toHaveBeenCalledTimes(4);
|
||||
expect(onUndefinedB).toHaveBeenCalledTimes(4);
|
||||
|
||||
// @ts-ignore
|
||||
const something = jest.fn();
|
||||
// @ts-ignore
|
||||
addEvent('something', something);
|
||||
// @ts-ignore
|
||||
removeEvent('something');
|
||||
// @ts-ignore
|
||||
triggerEvent('something');
|
||||
expect(something).not.toHaveBeenCalled();
|
||||
|
||||
// @ts-ignore
|
||||
removeEvent('not previously added');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user