improve event listener code and tests

This commit is contained in:
Rene
2022-07-11 17:07:34 +02:00
parent 60dd573cfc
commit 295f94309f
4 changed files with 203 additions and 38 deletions
@@ -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');
});
});
});