Spaces:
Running
Running
| import { describe, vi, it, expect, afterEach } from 'vitest' | |
| import { | |
| Interceptor, | |
| getGlobalSymbol, | |
| deleteGlobalSymbol, | |
| InterceptorReadyState, | |
| } from './Interceptor' | |
| import { nextTickAsync } from './utils/nextTick' | |
| const symbol = Symbol('test') | |
| afterEach(() => { | |
| deleteGlobalSymbol(symbol) | |
| }) | |
| it('does not set a maximum listeners limit', () => { | |
| const interceptor = new Interceptor(symbol) | |
| expect(interceptor['emitter'].getMaxListeners()).toBe(0) | |
| }) | |
| describe('on()', () => { | |
| it('adds a new listener using "on()"', () => { | |
| const interceptor = new Interceptor(symbol) | |
| expect(interceptor['emitter'].listenerCount('event')).toBe(0) | |
| const listener = vi.fn() | |
| interceptor.on('event', listener) | |
| expect(interceptor['emitter'].listenerCount('event')).toBe(1) | |
| }) | |
| }) | |
| describe('once()', () => { | |
| it('calls the listener only once', () => { | |
| const interceptor = new Interceptor(symbol) | |
| const listener = vi.fn() | |
| interceptor.once('foo', listener) | |
| expect(listener).not.toHaveBeenCalled() | |
| interceptor['emitter'].emit('foo', 'bar') | |
| expect(listener).toHaveBeenCalledTimes(1) | |
| expect(listener).toHaveBeenCalledWith('bar') | |
| listener.mockReset() | |
| interceptor['emitter'].emit('foo', 'baz') | |
| interceptor['emitter'].emit('foo', 'xyz') | |
| expect(listener).toHaveBeenCalledTimes(0) | |
| }) | |
| }) | |
| describe('off()', () => { | |
| it('removes a listener using "off()"', () => { | |
| const interceptor = new Interceptor(symbol) | |
| expect(interceptor['emitter'].listenerCount('event')).toBe(0) | |
| const listener = vi.fn() | |
| interceptor.on('event', listener) | |
| expect(interceptor['emitter'].listenerCount('event')).toBe(1) | |
| interceptor.off('event', listener) | |
| expect(interceptor['emitter'].listenerCount('event')).toBe(0) | |
| }) | |
| }) | |
| describe('persistence', () => { | |
| it('stores global reference to the applied interceptor', () => { | |
| const interceptor = new Interceptor(symbol) | |
| interceptor.apply() | |
| expect(getGlobalSymbol(symbol)).toEqual(interceptor) | |
| }) | |
| it('deletes global reference when the interceptor is disposed', () => { | |
| const interceptor = new Interceptor(symbol) | |
| interceptor.apply() | |
| interceptor.dispose() | |
| expect(getGlobalSymbol(symbol)).toBeUndefined() | |
| }) | |
| }) | |
| describe('readyState', () => { | |
| it('sets the state to "INACTIVE" when the interceptor is created', () => { | |
| const interceptor = new Interceptor(symbol) | |
| expect(interceptor.readyState).toBe(InterceptorReadyState.INACTIVE) | |
| }) | |
| it('leaves state as "INACTIVE" if the interceptor failed the environment check', async () => { | |
| class MyInterceptor extends Interceptor<any> { | |
| protected checkEnvironment(): boolean { | |
| return false | |
| } | |
| } | |
| const interceptor = new MyInterceptor(symbol) | |
| interceptor.apply() | |
| expect(interceptor.readyState).toBe(InterceptorReadyState.INACTIVE) | |
| }) | |
| it('perfroms state transition when the interceptor is applying', async () => { | |
| const interceptor = new Interceptor(symbol) | |
| interceptor.apply() | |
| // The interceptor's state transitions to APPLIED immediately. | |
| // The only exception is if something throws during the setup. | |
| expect(interceptor.readyState).toBe(InterceptorReadyState.APPLIED) | |
| }) | |
| it('perfroms state transition when disposing of the interceptor', async () => { | |
| const interceptor = new Interceptor(symbol) | |
| interceptor.apply() | |
| interceptor.dispose() | |
| // The interceptor's state transitions to DISPOSED immediately. | |
| // The only exception is if something throws during the teardown. | |
| expect(interceptor.readyState).toBe(InterceptorReadyState.DISPOSED) | |
| }) | |
| }) | |
| describe('apply', () => { | |
| it('does not apply the same interceptor multiple times', () => { | |
| const interceptor = new Interceptor(symbol) | |
| const setupSpy = vi.spyOn( | |
| interceptor, | |
| // @ts-expect-error Protected property spy. | |
| 'setup' | |
| ) | |
| // Intentionally apply the same interceptor multiple times. | |
| interceptor.apply() | |
| interceptor.apply() | |
| interceptor.apply() | |
| // The "setup" must not be called repeatedly. | |
| expect(setupSpy).toHaveBeenCalledTimes(1) | |
| expect(getGlobalSymbol(symbol)).toEqual(interceptor) | |
| }) | |
| it('does not call "apply" if the interceptor fails environment check', () => { | |
| class MyInterceptor extends Interceptor<{}> { | |
| checkEnvironment() { | |
| return false | |
| } | |
| } | |
| const interceptor = new MyInterceptor(Symbol('test')) | |
| const setupSpy = vi.spyOn( | |
| interceptor, | |
| // @ts-expect-error Protected property spy. | |
| 'setup' | |
| ) | |
| interceptor.apply() | |
| expect(setupSpy).not.toHaveBeenCalled() | |
| }) | |
| it('proxies listeners from new interceptor to already running interceptor', () => { | |
| const firstInterceptor = new Interceptor(symbol) | |
| const secondInterceptor = new Interceptor(symbol) | |
| firstInterceptor.apply() | |
| const firstListener = vi.fn() | |
| firstInterceptor.on('test', firstListener) | |
| secondInterceptor.apply() | |
| const secondListener = vi.fn() | |
| secondInterceptor.on('test', secondListener) | |
| // Emitting event in the first interceptor will bubble to the second one. | |
| firstInterceptor['emitter'].emit('test', 'hello world') | |
| expect(firstListener).toHaveBeenCalledTimes(1) | |
| expect(firstListener).toHaveBeenCalledWith('hello world') | |
| expect(secondListener).toHaveBeenCalledTimes(1) | |
| expect(secondListener).toHaveBeenCalledWith('hello world') | |
| expect(secondInterceptor['emitter'].listenerCount('test')).toBe(0) | |
| }) | |
| }) | |
| describe('dispose', () => { | |
| it('removes all listeners when the interceptor is disposed', async () => { | |
| const interceptor = new Interceptor(symbol) | |
| interceptor.apply() | |
| const listener = vi.fn() | |
| interceptor.on('test', listener) | |
| interceptor.dispose() | |
| // Even after emitting an event, the listener must not get called. | |
| interceptor['emitter'].emit('test') | |
| expect(listener).not.toHaveBeenCalled() | |
| // The listener must not be called on the next tick either. | |
| await nextTickAsync(() => { | |
| interceptor['emitter'].emit('test') | |
| expect(listener).not.toHaveBeenCalled() | |
| }) | |
| }) | |
| }) | |