From f1cb1a66a65423201ff8f5998a3b89e0c80a32df Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Wed, 8 Dec 2021 22:00:51 +0100 Subject: [PATCH] refactor(core): unify focusManager and onlineManager into an eventManager, which is function based --- src/core/eventManager.ts | 78 ++++++++++++++++++++++ src/core/focusManager.ts | 95 +++++++-------------------- src/core/onlineManager.ts | 95 +++++++-------------------- src/core/tests/focusManager.test.tsx | 12 ++-- src/core/tests/onlineManager.test.tsx | 12 ++-- 5 files changed, 140 insertions(+), 152 deletions(-) create mode 100644 src/core/eventManager.ts diff --git a/src/core/eventManager.ts b/src/core/eventManager.ts new file mode 100644 index 0000000000..be94551dd9 --- /dev/null +++ b/src/core/eventManager.ts @@ -0,0 +1,78 @@ +import { isServer } from './utils' + +type ListenerFn = () => void + +export function createEventManager( + events: ReadonlyArray[0]> +) { + let value: boolean | undefined + let removeEventListener: ListenerFn | undefined + let listeners: ListenerFn[] = [] + let setupFn: ( + param: (newValue?: boolean) => void + ) => ListenerFn | undefined = onEvent => { + if (!isServer && window?.addEventListener) { + const listener = () => onEvent() + events.forEach(eventName => { + window.addEventListener(eventName, listener, false) + }) + + return () => { + events.forEach(eventName => { + window.removeEventListener(eventName, listener) + }) + } + } + } + + const subscribe = (listener: ListenerFn): ListenerFn => { + listeners.push(listener) + + if (!removeEventListener) { + setEventListener(setupFn) + } + + return () => { + listeners = listeners.filter(x => x !== listener) + if (listeners.length === 0) { + removeEventListener?.() + removeEventListener = undefined + } + } + } + + const setValue = (newValue?: boolean): void => { + value = newValue + + if (newValue) { + onEvent() + } + } + + const setEventListener = ( + setup: (param: (newValue?: boolean) => void) => ListenerFn | undefined + ): void => { + removeEventListener?.() + setupFn = setup + removeEventListener = setupFn(newValue => { + if (typeof newValue === 'boolean') { + setValue(newValue) + } else { + onEvent() + } + }) + } + + const onEvent = (): void => { + listeners.forEach(listener => { + listener() + }) + } + + return { + setEventListener, + subscribe, + setValue, + getValue: () => value, + } as const +} diff --git a/src/core/focusManager.ts b/src/core/focusManager.ts index 18bfdcbc46..7a98cf03a2 100644 --- a/src/core/focusManager.ts +++ b/src/core/focusManager.ts @@ -1,76 +1,31 @@ -import { Subscribable } from './subscribable' -import { isServer } from './utils' - -class FocusManager extends Subscribable { - private focused?: boolean - private removeEventListener?: () => void - - protected onSubscribe(): void { - if (!this.removeEventListener) { - this.setDefaultEventListener() - } - } - - setEventListener( - setup: (setFocused: (focused?: boolean) => void) => () => void - ): void { - if (this.removeEventListener) { - this.removeEventListener() - } - this.removeEventListener = setup(focused => { - if (typeof focused === 'boolean') { - this.setFocused(focused) - } else { - this.onFocus() +import { createEventManager } from './eventManager' + +export const createFocusManager = () => { + const { setEventListener, subscribe, ...manager } = createEventManager([ + 'visibilitychange', + 'focus', + ]) + + return { + subscribe, + setEventListener, + setFocused: manager.setValue, + isFocused: (): boolean => { + const value = manager.getValue() + if (typeof value === 'boolean') { + return value } - }) - } - - setFocused(focused?: boolean): void { - this.focused = focused - - if (focused) { - this.onFocus() - } - } - onFocus(): void { - this.listeners.forEach(listener => { - listener() - }) - } - - isFocused(): boolean { - if (typeof this.focused === 'boolean') { - return this.focused - } - - // document global can be unavailable in react native - if (typeof document === 'undefined') { - return true - } - - return [undefined, 'visible', 'prerender'].includes( - document.visibilityState - ) - } - - private setDefaultEventListener() { - if (!isServer && window?.addEventListener) { - this.setEventListener(onFocus => { - const listener = () => onFocus() - // Listen to visibillitychange and focus - window.addEventListener('visibilitychange', listener, false) - window.addEventListener('focus', listener, false) + // document global can be unavailable in react native + if (typeof document === 'undefined') { + return true + } - return () => { - // Be sure to unsubscribe if a new handler is set - window.removeEventListener('visibilitychange', listener) - window.removeEventListener('focus', listener) - } - }) - } + return [undefined, 'visible', 'prerender'].includes( + document.visibilityState + ) + }, } } -export const focusManager = new FocusManager() +export const focusManager = createFocusManager() diff --git a/src/core/onlineManager.ts b/src/core/onlineManager.ts index f02a03c9bf..5c3f45cdeb 100644 --- a/src/core/onlineManager.ts +++ b/src/core/onlineManager.ts @@ -1,76 +1,31 @@ -import { Subscribable } from './subscribable' -import { isServer } from './utils' - -class OnlineManager extends Subscribable { - private online?: boolean - private removeEventListener?: () => void - - protected onSubscribe(): void { - if (!this.removeEventListener) { - this.setDefaultEventListener() - } - } - - setEventListener( - setup: (setOnline: (online?: boolean) => void) => () => void - ): void { - if (this.removeEventListener) { - this.removeEventListener() - } - this.removeEventListener = setup((online?: boolean) => { - if (typeof online === 'boolean') { - this.setOnline(online) - } else { - this.onOnline() +import { createEventManager } from './eventManager' + +export const createOnlineManager = () => { + const { setEventListener, subscribe, ...manager } = createEventManager([ + 'online', + 'offline', + ]) + + return { + subscribe, + setEventListener, + setOnline: manager.setValue, + isOnline: (): boolean => { + const value = manager.getValue() + if (typeof value === 'boolean') { + return value } - }) - } - - setOnline(online?: boolean): void { - this.online = online - - if (online) { - this.onOnline() - } - } - onOnline(): void { - this.listeners.forEach(listener => { - listener() - }) - } - - isOnline(): boolean { - if (typeof this.online === 'boolean') { - return this.online - } - - if ( - typeof navigator === 'undefined' || - typeof navigator.onLine === 'undefined' - ) { - return true - } - - return navigator.onLine - } - - private setDefaultEventListener() { - if (!isServer && window?.addEventListener) { - this.setEventListener(onOnline => { - const listener = () => onOnline() - // Listen to online - window.addEventListener('online', listener, false) - window.addEventListener('offline', listener, false) + if ( + typeof navigator === 'undefined' || + typeof navigator.onLine === 'undefined' + ) { + return true + } - return () => { - // Be sure to unsubscribe if a new handler is set - window.removeEventListener('online', listener) - window.removeEventListener('offline', listener) - } - }) - } + return navigator.onLine + }, } } -export const onlineManager = new OnlineManager() +export const onlineManager = createOnlineManager() diff --git a/src/core/tests/focusManager.test.tsx b/src/core/tests/focusManager.test.tsx index ca071ec16d..1d45c81cdd 100644 --- a/src/core/tests/focusManager.test.tsx +++ b/src/core/tests/focusManager.test.tsx @@ -1,10 +1,10 @@ import { sleep } from '../utils' -import { focusManager } from '../focusManager' +import { createFocusManager } from '../focusManager' describe('focusManager', () => { - afterEach(() => { - // Reset removeEventListener private property to avoid side effects between tests - focusManager['removeEventListener'] = undefined + let focusManager: ReturnType + beforeEach(() => { + focusManager = createFocusManager() }) it('should call previous remove handler when replacing an event listener', () => { @@ -69,7 +69,7 @@ describe('focusManager', () => { const setEventListenerSpy = jest.spyOn(focusManager, 'setEventListener') - const unsubscribe = focusManager.subscribe() + const unsubscribe = focusManager.subscribe(() => undefined) expect(setEventListenerSpy).toHaveBeenCalledTimes(0) unsubscribe() @@ -88,7 +88,7 @@ describe('focusManager', () => { ) // Should set the default event listener with window event listeners - const unsubscribe = focusManager.subscribe() + const unsubscribe = focusManager.subscribe(() => undefined) expect(addEventListenerSpy).toHaveBeenCalledTimes(2) // Should replace the window default event listener by a new one diff --git a/src/core/tests/onlineManager.test.tsx b/src/core/tests/onlineManager.test.tsx index 2e7d35c3f4..b7e36b6e9d 100644 --- a/src/core/tests/onlineManager.test.tsx +++ b/src/core/tests/onlineManager.test.tsx @@ -1,10 +1,10 @@ -import { onlineManager } from '../onlineManager' +import { createOnlineManager } from '../onlineManager' import { sleep } from '../utils' describe('onlineManager', () => { - afterEach(() => { - // Reset removeEventListener private property to avoid side effects between tests - onlineManager['removeEventListener'] = undefined + let onlineManager: ReturnType + beforeEach(() => { + onlineManager = createOnlineManager() }) test('isOnline should return true if navigator is undefined', () => { @@ -64,7 +64,7 @@ describe('onlineManager', () => { const setEventListenerSpy = jest.spyOn(onlineManager, 'setEventListener') - const unsubscribe = onlineManager.subscribe() + const unsubscribe = onlineManager.subscribe(() => undefined) expect(setEventListenerSpy).toHaveBeenCalledTimes(0) unsubscribe() @@ -83,7 +83,7 @@ describe('onlineManager', () => { ) // Should set the default event listener with window event listeners - const unsubscribe = onlineManager.subscribe() + const unsubscribe = onlineManager.subscribe(() => undefined) expect(addEventListenerSpy).toHaveBeenCalledTimes(2) // Should replace the window default event listener by a new one