Spaces:
Paused
Paused
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() | |
}) | |
}) | |
}) | |