import { datadogRum } from '@datadog/browser-rum'; import { minimalApplication } from '../__mocks__'; import { buildAllowedTracingUrls } from '../build-allowed-tracing-urls'; import { buildBeforeSend } from '../build-before-send'; import { createImpl } from '../create-impl'; import { DATADOG_RUM_FOR_DEV, installDevConsole, isForceEnabled } from '../runtime-override'; import type { SharedImpl } from '../shared-impl'; import type { InitRumOptions, State } from '../types'; jest.mock('@datadog/browser-rum'); jest.mock('../build-before-send'); jest.mock('../build-allowed-tracing-urls'); jest.mock('../runtime-override', () => ({ ...jest.requireActual('../runtime-override'), isForceEnabled: jest.fn(), installDevConsole: jest.fn(), })); describe(`[datadog-rum] ${createImpl.name}`, () => { const mockBeforeSend = jest.fn(); const mockAllowedTracingUrls = jest.fn(); let impl: SharedImpl; beforeEach(() => { jest.clearAllMocks(); jest.mocked(datadogRum.getInitConfiguration).mockReturnValue(undefined); jest.mocked(isForceEnabled).mockReturnValue(false); jest.mocked(buildBeforeSend).mockReturnValue(mockBeforeSend); jest.mocked(buildAllowedTracingUrls).mockReturnValue(mockAllowedTracingUrls as any); impl = createImpl(); }); /* * createImpl passes the same live state object to both collaborators, so the * captured argument reflects later mutations from init/setServiceContext/cleanup. */ const capturedState = () => jest.mocked(buildBeforeSend).mock.calls[0][0] as State; describe('init', () => { let options: InitRumOptions; beforeEach(() => { options = { service: { name: 'desktop' }, application: { ...minimalApplication } }; }); const subject = () => impl.init(options); test('calls datadogRum.init', () => { subject(); expect(datadogRum.init).toHaveBeenCalledWith( expect.objectContaining({ ...options.application, sessionSampleRate: 100, sessionReplaySampleRate: 0, defaultPrivacyLevel: 'mask-user-input', service: options.service.name, beforeSend: mockBeforeSend, allowedTracingUrls: mockAllowedTracingUrls, }) ); }); test('reports it initialized the SDK', () => { expect(subject().initializedSdk).toBe(true); }); test('registers the service', () => { subject(); expect(capturedState().services.has(options.service.name)).toBe(true); }); itDoesNotInstallDevConsole(); itDoesNotTagTheSession(); describe('with a stale override present', () => { beforeEach(() => { jest.mocked(isForceEnabled).mockReturnValue(true); }); itDoesNotTagTheSession(); }); describe('when config overrides defaults', () => { const overrides = { sessionSampleRate: 25, sessionReplaySampleRate: 10, defaultPrivacyLevel: 'allow', } as const; beforeEach(() => { options.application = { ...minimalApplication, ...overrides }; }); test('calls datadogRum.init with overridden values', () => { subject(); expect(datadogRum.init).toHaveBeenCalledWith(expect.objectContaining(overrides)); }); }); describe('when config contains extra values', () => { const excludedActivityUrls = [/pendo\.io/]; beforeEach(() => { options.application = { ...minimalApplication, excludedActivityUrls }; }); test('calls datadogRum.init with extra values', () => { subject(); expect(datadogRum.init).toHaveBeenCalledWith( expect.objectContaining({ excludedActivityUrls }) ); }); }); describe('with an application beforeSend handler', () => { const appBeforeSend = jest.fn(); beforeEach(() => { options.application = { ...minimalApplication, beforeSend: appBeforeSend }; }); test('registers the handler', () => { subject(); expect(capturedState().applicationBeforeSend).toBe(appBeforeSend); }); }); function itDoesNotCallDatadogRumInit() { test('does not call datadogRum.init', () => { subject(); expect(datadogRum.init).not.toHaveBeenCalled(); }); test('reports it did not initialize the SDK', () => { expect(subject().initializedSdk).toBe(false); }); } function itDoesNotInstallDevConsole() { test('does not install the dev console handle', () => { subject(); expect(installDevConsole).not.toHaveBeenCalled(); }); } function itDoesNotTagTheSession() { test('does not tag the session', () => { subject(); expect(datadogRum.setGlobalContextProperty).not.toHaveBeenCalled(); }); } describe('when already called', () => { beforeEach(() => { subject(); jest.clearAllMocks(); jest.spyOn(console, 'info').mockImplementation(() => {}); }); itDoesNotCallDatadogRumInit(); test('logs an info message', () => { const infoSpy = jest.spyOn(console, 'info'); subject(); expect(infoSpy).toHaveBeenCalledWith( expect.stringContaining('application config ignored') ); }); }); describe('when datadogRum.init was already called directly', () => { beforeEach(() => { jest.spyOn(console, 'warn').mockImplementation(() => {}); jest.mocked(datadogRum.getInitConfiguration).mockReturnValue({} as any); }); itDoesNotCallDatadogRumInit(); test('warns the SDK was initialized outside the facade', () => { const warnSpy = jest.spyOn(console, 'warn'); subject(); expect(warnSpy).toHaveBeenCalledWith( expect.stringContaining('SDK was initialized outside the facade') ); }); }); describe('with service only (no application)', () => { beforeEach(() => { delete options.application; }); itDoesNotCallDatadogRumInit(); }); describe('when enabled is false', () => { beforeEach(() => { options.enabled = false; }); test('installs the dev console handle', () => { subject(); expect(installDevConsole).toHaveBeenCalled(); }); itDoesNotCallDatadogRumInit(); describe('with an active override', () => { beforeEach(() => { jest.mocked(isForceEnabled).mockReturnValue(true); }); test('initializes the SDK', () => { subject(); expect(datadogRum.init).toHaveBeenCalled(); }); test('reports it initialized the SDK', () => { expect(subject().initializedSdk).toBe(true); }); test('tags the session as override-driven', () => { subject(); expect(datadogRum.setGlobalContextProperty).toHaveBeenCalledWith( DATADOG_RUM_FOR_DEV, true ); }); }); describe('with no application', () => { beforeEach(() => { delete options.application; }); itDoesNotInstallDevConsole(); }); }); describe('cleanup', () => { beforeEach(() => { subject().cleanup(); }); test('unregisters the service', () => { expect(capturedState().services.has(options.service.name)).toBe(false); }); }); }); describe('setServiceContext', () => { const name = 'task-hub'; const context = { orgId: '42' }; beforeEach(() => { impl.init({ service: { name }, application: minimalApplication }); }); const subject = () => impl.setServiceContext(name, context); test('registers service context', () => { subject(); expect(capturedState().services.get(name)?.context).toEqual(context); }); describe('when already called', () => { beforeEach(() => subject()); test('init() preserves context', () => { impl.init({ service: { name, beforeSend: jest.fn() } }); expect(capturedState().services.get(name)?.context).toEqual(context); }); }); }); });