diff --git a/packages/core/src/analytics/__tests__/dispatch.test.ts b/packages/core/src/analytics/__tests__/dispatch.test.ts new file mode 100644 index 000000000..18efbb70f --- /dev/null +++ b/packages/core/src/analytics/__tests__/dispatch.test.ts @@ -0,0 +1,103 @@ +// import { CoreContext } from '../context' +// import { CoreSegmentEvent, Callback } from '../events/interfaces' + +const isOnline = jest.fn().mockReturnValue(true) +const isOffline = jest.fn().mockReturnValue(false) + +jest.mock('../../connection', () => ({ + isOnline, + isOffline, +})) +const fetcher = jest.fn() +jest.mock('node-fetch', () => fetcher) + +const invokeCallback = jest.fn() + +jest.mock('../../callback', () => ({ + invokeCallback: invokeCallback, +})) + +import { EventQueue } from '../../queue/event-queue' +import { Emitter } from '../../emitter' +import { dispatch, getDelayTimeout } from '../dispatch' +import { PriorityQueue } from '../../priority-queue' + +let emitter!: Emitter +let queue!: EventQueue +const dispatchSingleSpy = jest.spyOn(EventQueue.prototype, 'dispatchSingle') +const dispatchSpy = jest.spyOn(EventQueue.prototype, 'dispatch') + +beforeEach(() => { + queue = new EventQueue(new PriorityQueue(4, [])) + emitter = new Emitter() +}) + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('Dispatch', () => { + it('should return ctx if offline and retryQueue is false', async () => { + isOnline.mockReturnValue(false) + isOffline.mockReturnValue(true) + + const ctx = await dispatch({ type: 'screen' }, queue, emitter, { + retryQueue: false, + }) + expect(ctx.event.type).toBe('screen') + const called = Boolean( + dispatchSingleSpy.mock.calls.length || dispatchSpy.mock.calls.length + ) + expect(called).toBeFalsy() + }) + + it('should not dispatch if offline and retryQueue is true', async () => { + isOnline.mockReturnValue(false) + isOffline.mockReturnValue(true) + + await dispatch({ type: 'screen' }, queue, emitter, { + retryQueue: true, + }) + const called = Boolean( + dispatchSingleSpy.mock.calls.length || dispatchSpy.mock.calls.length + ) + expect(called).toBeTruthy() + }) + + it('should call dispatchSingle with ctx if queue is empty', async () => { + queue.isEmpty = jest.fn().mockReturnValue(true) + await dispatch({ type: 'screen' }, queue, emitter) + expect(dispatchSingleSpy).toBeCalledWith( + expect.objectContaining({ event: { type: 'screen' } }) + ) + expect(dispatchSpy).not.toBeCalled() + }) + it('should call dispatch with ctx if queue is empty', async () => { + queue.isEmpty = jest.fn().mockReturnValue(false) + await dispatch({ type: 'screen' }, queue, emitter) + expect(dispatchSpy).toBeCalledWith( + expect.objectContaining({ event: { type: 'screen' } }) + ) + expect(dispatchSingleSpy).not.toBeCalled() + }) + it('should not invoke callback if no callback is passed', async () => { + await dispatch({ type: 'screen' }, queue, emitter) + expect(invokeCallback).not.toBeCalled() + }) + it('should call "invokeCallback" if callback is passed', async () => { + const cb = jest.fn() + await dispatch({ type: 'screen' }, queue, emitter, { callback: cb }) + expect(invokeCallback).toBeCalled() + }) +}) + +describe('getDelayTimeout', () => { + it('should work as expected', () => { + const aShortTimeAgo = Date.now() - 200 + expect(Math.round(getDelayTimeout(aShortTimeAgo, 500))).toBe(300) + }) + it('should have a sensible default', () => { + const aShortTimeAgo = Date.now() - 200 + expect(Math.round(getDelayTimeout(aShortTimeAgo))).toBe(100) + }) +}) diff --git a/packages/core/src/analytics/dispatch.ts b/packages/core/src/analytics/dispatch.ts index 3645d02af..ccde20b45 100644 --- a/packages/core/src/analytics/dispatch.ts +++ b/packages/core/src/analytics/dispatch.ts @@ -12,6 +12,14 @@ type DispatchOptions = { retryQueue?: boolean } +/* The amount of time in ms to wait before invoking the callback. */ +export const getDelayTimeout = ( + startTimeInEpochMS: number, + timeoutInMS?: number +) => { + const elapsedTime = Date.now() - startTimeInEpochMS + return Math.max((timeoutInMS ?? 300) - elapsedTime, 0) +} /** * Push an event into the dispatch queue and invoke any callbacks. * @@ -40,14 +48,13 @@ export async function dispatch( } else { dispatched = await queue.dispatch(ctx) } - const elapsedTime = Date.now() - startTime const timeoutInMs = options?.timeout if (options?.callback) { dispatched = await invokeCallback( dispatched, options.callback, - Math.max((timeoutInMs ?? 300) - elapsedTime, 0), + getDelayTimeout(startTime, timeoutInMs), timeoutInMs ) }