diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index 39f38b85c3..6db6f8be43 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -8,9 +8,14 @@ import { CustomerDataCompressionStatus, timeStampToClocks, } from '@datadog/browser-core' -import { cleanupSyntheticsWorkerValues, mockExperimentalFeatures } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../test' -import { setup, noopRecorderApi } from '../../test' +import type { Clock } from '@datadog/browser-core/test' +import { + cleanupSyntheticsWorkerValues, + mockClock, + mockExperimentalFeatures, + registerCleanupTask, +} from '@datadog/browser-core/test' +import { noopRecorderApi } from '../../test' import { ActionType, VitalType } from '../rawRumEvent.types' import type { DurationVitalReference } from '../domain/vital/vitalCollection' import type { RumPublicApi, RecorderApi } from './rumPublicApi' @@ -43,10 +48,7 @@ describe('rum public api', () => { beforeEach(() => { startRumSpy = jasmine.createSpy().and.callFake(noopStartRum) - }) - - afterEach(() => { - cleanupSyntheticsWorkerValues() + registerCleanupTask(cleanupSyntheticsWorkerValues) }) describe('deflate worker', () => { @@ -156,7 +158,7 @@ describe('rum public api', () => { describe('addAction', () => { let addActionSpy: jasmine.Spy['addAction']> let rumPublicApi: RumPublicApi - let setupBuilder: TestSetupBuilder + let clock: Clock beforeEach(() => { addActionSpy = jasmine.createSpy() @@ -167,7 +169,11 @@ describe('rum public api', () => { }), noopRecorderApi ) - setupBuilder = setup() + clock = mockClock() + + registerCleanupTask(() => { + clock.cleanup() + }) }) it('allows sending actions before init', () => { @@ -205,8 +211,6 @@ describe('rum public api', () => { describe('save context when sending an action', () => { it('saves the date', () => { - const { clock } = setupBuilder.withFakeClock().build() - clock.tick(ONE_SECOND) rumPublicApi.addAction('foo') @@ -258,7 +262,7 @@ describe('rum public api', () => { describe('addError', () => { let addErrorSpy: jasmine.Spy['addError']> let rumPublicApi: RumPublicApi - let setupBuilder: TestSetupBuilder + let clock: Clock beforeEach(() => { addErrorSpy = jasmine.createSpy() @@ -269,7 +273,11 @@ describe('rum public api', () => { }), noopRecorderApi ) - setupBuilder = setup() + clock = mockClock() + + registerCleanupTask(() => { + clock.cleanup() + }) }) it('allows capturing an error before init', () => { @@ -305,8 +313,6 @@ describe('rum public api', () => { describe('save context when capturing an error', () => { it('saves the date', () => { - const { clock } = setupBuilder.withFakeClock().build() - clock.tick(ONE_SECOND) rumPublicApi.addError(new Error('foo')) @@ -668,11 +674,11 @@ describe('rum public api', () => { beforeEach(() => { rumPublicApi = makeRumPublicApi(noopStartRum, noopRecorderApi) - }) - afterEach(() => { - localStorage.clear() - removeStorageListeners() + registerCleanupTask(() => { + localStorage.clear() + removeStorageListeners() + }) }) it('when disabled, should store contexts only in memory', () => { diff --git a/packages/rum-core/src/boot/startRum.spec.ts b/packages/rum-core/src/boot/startRum.spec.ts index 357f01a462..d8c816b423 100644 --- a/packages/rum-core/src/boot/startRum.spec.ts +++ b/packages/rum-core/src/boot/startRum.spec.ts @@ -1,5 +1,6 @@ -import type { Observable, RawError, Duration, RelativeTime } from '@datadog/browser-core' +import type { RawError, Duration, RelativeTime } from '@datadog/browser-core' import { + Observable, stopSessionManager, toServerDuration, ONE_SECOND, @@ -11,19 +12,28 @@ import { createCustomerDataTracker, createTrackingConsentState, TrackingConsent, + createCustomerDataTrackerManager, } from '@datadog/browser-core' -import { createNewEvent, interceptRequests, mockEventBridge } from '@datadog/browser-core/test' -import type { RumSessionManagerMock, TestSetupBuilder } from '../../test' +import type { Clock } from '@datadog/browser-core/test' +import { + createNewEvent, + interceptRequests, + mockClock, + mockEventBridge, + registerCleanupTask, +} from '@datadog/browser-core/test' +import type { RumSessionManagerMock } from '../../test' import { createPerformanceEntry, createRumSessionManagerMock, + mockPageStateHistory, mockPerformanceObserver, + mockRumConfiguration, noopRecorderApi, - setup, + setupLocationObserver, } from '../../test' import { RumPerformanceEntryType } from '../browser/performanceObservable' -import type { LifeCycle } from '../domain/lifeCycle' -import { LifeCycleEventType } from '../domain/lifeCycle' +import { LifeCycle, LifeCycleEventType } from '../domain/lifeCycle' import { SESSION_KEEP_ALIVE_INTERVAL, THROTTLE_VIEW_UPDATE_PERIOD } from '../domain/view/trackViews' import { startViewCollection } from '../domain/view/viewCollection' import type { RumEvent, RumViewEvent } from '../rumEvent.types' @@ -91,43 +101,36 @@ function startRumStub( } describe('rum session', () => { - let setupBuilder: TestSetupBuilder let serverRumEvents: RumEvent[] + let lifeCycle: LifeCycle + let sessionManager: RumSessionManagerMock beforeEach(() => { if (isIE()) { pending('no full rum support') } - setupBuilder = setup().beforeBuild( - ({ - location, - lifeCycle, - configuration, - sessionManager, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - }) => { - serverRumEvents = collectServerEvents(lifeCycle) - return startRumStub( - lifeCycle, - configuration, - sessionManager, - location, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - noop - ) - } + lifeCycle = new LifeCycle() + sessionManager = createRumSessionManagerMock().setId('42') + const domMutationObservable = new Observable() + const { locationChangeObservable } = setupLocationObserver() + + serverRumEvents = collectServerEvents(lifeCycle) + const { stop } = startRumStub( + lifeCycle, + mockRumConfiguration(), + sessionManager, + location, + domMutationObservable, + locationChangeObservable, + mockPageStateHistory(), + noop ) + + registerCleanupTask(stop) }) it('when the session is renewed, a new view event should be sent', () => { - const session = createRumSessionManagerMock().setId('42') - const { lifeCycle } = setupBuilder.withSessionManager(session).build() - expect(serverRumEvents.length).toEqual(1) expect(serverRumEvents[0].type).toEqual('view') expect(serverRumEvents[0].session.id).toEqual('42') @@ -135,7 +138,7 @@ describe('rum session', () => { lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(serverRumEvents.length).toEqual(2) - session.setId('43') + sessionManager.setId('43') lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(serverRumEvents.length).toEqual(3) @@ -148,46 +151,40 @@ describe('rum session', () => { }) describe('rum session keep alive', () => { + let lifeCycle: LifeCycle + let clock: Clock let sessionManager: RumSessionManagerMock - let setupBuilder: TestSetupBuilder let serverRumEvents: RumEvent[] beforeEach(() => { if (isIE()) { pending('no full rum support') } + lifeCycle = new LifeCycle() + clock = mockClock() sessionManager = createRumSessionManagerMock().setId('1234') - setupBuilder = setup() - .withFakeClock() - .withSessionManager(sessionManager) - .beforeBuild( - ({ - location, - lifeCycle, - configuration, - sessionManager, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - }) => { - serverRumEvents = collectServerEvents(lifeCycle) - return startRumStub( - lifeCycle, - configuration, - sessionManager, - location, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - noop - ) - } - ) + const domMutationObservable = new Observable() + const { locationChangeObservable } = setupLocationObserver() + + serverRumEvents = collectServerEvents(lifeCycle) + const { stop } = startRumStub( + lifeCycle, + mockRumConfiguration(), + sessionManager, + location, + domMutationObservable, + locationChangeObservable, + mockPageStateHistory(), + noop + ) + + registerCleanupTask(() => { + stop() + clock.cleanup() + }) }) it('should send a view update regularly', () => { - const { clock } = setupBuilder.build() - // clear initial events clock.tick(SESSION_KEEP_ALIVE_INTERVAL * 0.9) serverRumEvents.length = 0 @@ -206,8 +203,6 @@ describe('rum session keep alive', () => { }) it('should not send view update when sessionManager is expired', () => { - const { clock } = setupBuilder.build() - // clear initial events clock.tick(SESSION_KEEP_ALIVE_INTERVAL * 0.9) serverRumEvents.length = 0 @@ -224,40 +219,56 @@ describe('rum session keep alive', () => { describe('rum events url', () => { const VIEW_DURATION = 1000 - let setupBuilder: TestSetupBuilder + let changeLocation: (to: string) => void + let lifeCycle: LifeCycle + let clock: Clock let serverRumEvents: RumEvent[] + let stop: () => void + + function setupViewUrlTest() { + const sessionManager = createRumSessionManagerMock().setId('1234') + const domMutationObservable = new Observable() + const locationSetupResult = setupLocationObserver('http://foo.com/') + changeLocation = locationSetupResult.changeLocation + + const startResult = startRumStub( + lifeCycle, + mockRumConfiguration(), + sessionManager, + locationSetupResult.fakeLocation, + domMutationObservable, + locationSetupResult.locationChangeObservable, + mockPageStateHistory(), + noop + ) + + stop = startResult.stop + } beforeEach(() => { - setupBuilder = setup().beforeBuild( - ({ - location, - lifeCycle, - configuration, - sessionManager, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - }) => { - serverRumEvents = collectServerEvents(lifeCycle) - return startRumStub( - lifeCycle, - configuration, - sessionManager, - location, - domMutationObservable, - locationChangeObservable, - pageStateHistory, - noop - ) - } - ) + lifeCycle = new LifeCycle() + serverRumEvents = collectServerEvents(lifeCycle) + + registerCleanupTask(() => { + clock?.cleanup() + stop() + }) + }) + + it('should keep the same URL when updating a view ended by a URL change', () => { + setupViewUrlTest() + serverRumEvents.length = 0 + + changeLocation('/bar') + + expect(serverRumEvents.length).toEqual(2) + expect(serverRumEvents[0].view.url).toEqual('http://foo.com/') + expect(serverRumEvents[1].view.url).toEqual('http://foo.com/bar') }) it('should attach the url corresponding to the start of the event', () => { - const { lifeCycle, clock, changeLocation } = setupBuilder - .withFakeClock() - .withFakeLocation('http://foo.com/') - .build() + clock = mockClock() + setupViewUrlTest() clock.tick(10) changeLocation('http://foo.com/?bar=bar') clock.tick(10) @@ -280,21 +291,10 @@ describe('rum events url', () => { expect(longTaskEvent.view.url).toBe('http://foo.com/?bar=bar') }) - it('should keep the same URL when updating a view ended by a URL change', () => { - const { changeLocation } = setupBuilder.withFakeLocation('http://foo.com/').build() - - serverRumEvents.length = 0 - - changeLocation('/bar') - - expect(serverRumEvents.length).toEqual(2) - expect(serverRumEvents[0].view.url).toEqual('http://foo.com/') - expect(serverRumEvents[1].view.url).toEqual('http://foo.com/bar') - }) - it('should keep the same URL when updating an ended view', () => { + clock = mockClock() const { notifyPerformanceEntries } = mockPerformanceObserver() - const { clock, changeLocation } = setupBuilder.withFakeClock().withFakeLocation('http://foo.com/').build() + setupViewUrlTest() clock.tick(VIEW_DURATION) @@ -311,28 +311,35 @@ describe('rum events url', () => { }) describe('view events', () => { - let setupBuilder: TestSetupBuilder + let clock: Clock let interceptor: ReturnType - - beforeEach(() => { - setupBuilder = setup().beforeBuild(({ configuration, customerDataTrackerManager }) => - startRum( - configuration, - noopRecorderApi, - customerDataTrackerManager, - () => ({ user: {}, context: {}, hasReplay: undefined }), - undefined, - createIdentityEncoder, - createTrackingConsentState(TrackingConsent.GRANTED), - createCustomVitalsState() - ) + let stop: () => void + + function setupViewCollectionTest() { + const startResult = startRum( + mockRumConfiguration(), + noopRecorderApi, + createCustomerDataTrackerManager(), + () => ({ user: {}, context: {}, hasReplay: undefined }), + undefined, + createIdentityEncoder, + createTrackingConsentState(TrackingConsent.GRANTED), + createCustomVitalsState() ) + + stop = startResult.stop interceptor = interceptRequests() - }) + } - afterEach(() => { - stopSessionManager() - interceptor.restore() + beforeEach(() => { + clock = mockClock() + + registerCleanupTask(() => { + stop() + stopSessionManager() + interceptor.restore() + clock.cleanup() + }) }) it('sends a view update on page unload when bridge is absent', () => { @@ -342,7 +349,7 @@ describe('view events', () => { // Arbitrary duration to simulate a non-zero view duration const VIEW_DURATION = ONE_SECOND as Duration - const { clock } = setupBuilder.withFakeClock().build() + setupViewCollectionTest() clock.tick(VIEW_DURATION) window.dispatchEvent(createNewEvent('beforeunload')) @@ -364,7 +371,7 @@ describe('view events', () => { const VIEW_DURATION = ONE_SECOND as Duration - const { clock } = setupBuilder.withFakeClock().build() + setupViewCollectionTest() clock.tick(VIEW_DURATION) window.dispatchEvent(createNewEvent('beforeunload')) diff --git a/packages/rum-core/src/browser/cookieObservable.spec.ts b/packages/rum-core/src/browser/cookieObservable.spec.ts index a2981c3d5d..b9cc7e4ee5 100644 --- a/packages/rum-core/src/browser/cookieObservable.spec.ts +++ b/packages/rum-core/src/browser/cookieObservable.spec.ts @@ -2,7 +2,7 @@ import type { Subscription } from '@datadog/browser-core' import { ONE_MINUTE, deleteCookie, setCookie } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../domain/configuration' +import { mockRumConfiguration } from '../../test' import { WATCH_COOKIE_INTERVAL_DELAY, createCookieObservable } from './cookieObservable' const COOKIE_NAME = 'cookie_name' @@ -27,7 +27,7 @@ describe('cookieObservable', () => { }) it('should notify observers on cookie change', (done) => { - const observable = createCookieObservable({} as RumConfiguration, COOKIE_NAME) + const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME) subscription = observable.subscribe((cookieChange) => { expect(cookieChange).toEqual('foo') @@ -40,7 +40,7 @@ describe('cookieObservable', () => { it('should notify observers on cookie change when cookieStore is not supported', () => { Object.defineProperty(window, 'cookieStore', { get: () => undefined, configurable: true }) - const observable = createCookieObservable({} as RumConfiguration, COOKIE_NAME) + const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME) let cookieChange: string | undefined subscription = observable.subscribe((change) => (cookieChange = change)) @@ -53,7 +53,7 @@ describe('cookieObservable', () => { it('should not notify observers on cookie change when the cookie value as not changed when cookieStore is not supported', () => { Object.defineProperty(window, 'cookieStore', { get: () => undefined, configurable: true }) - const observable = createCookieObservable({} as RumConfiguration, COOKIE_NAME) + const observable = createCookieObservable(mockRumConfiguration(), COOKIE_NAME) setCookie(COOKIE_NAME, 'foo', COOKIE_DURATION) diff --git a/packages/rum-core/src/browser/performanceCollection.spec.ts b/packages/rum-core/src/browser/performanceCollection.spec.ts index 98c5255aca..fbacc017bb 100644 --- a/packages/rum-core/src/browser/performanceCollection.spec.ts +++ b/packages/rum-core/src/browser/performanceCollection.spec.ts @@ -1,18 +1,16 @@ import { registerCleanupTask } from '@datadog/browser-core/test' -import { createPerformanceEntry, mockPerformanceObserver } from '../../test' -import type { RumConfiguration } from '../domain/configuration' +import { createPerformanceEntry, mockPerformanceObserver, mockRumConfiguration } from '../../test' import { LifeCycle, LifeCycleEventType } from '../domain/lifeCycle' import { startPerformanceCollection } from './performanceCollection' import { RumPerformanceEntryType } from './performanceObservable' describe('startPerformanceCollection', () => { const lifeCycle = new LifeCycle() - const configuration = {} as RumConfiguration let entryCollectedCallback: jasmine.Spy function setupStartPerformanceCollection() { entryCollectedCallback = jasmine.createSpy() - const { stop } = startPerformanceCollection(lifeCycle, configuration) + const { stop } = startPerformanceCollection(lifeCycle, mockRumConfiguration()) const subscription = lifeCycle.subscribe(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, entryCollectedCallback) registerCleanupTask(() => { diff --git a/packages/rum-core/src/browser/performanceObservable.spec.ts b/packages/rum-core/src/browser/performanceObservable.spec.ts index cab7eaa12f..01fee67293 100644 --- a/packages/rum-core/src/browser/performanceObservable.spec.ts +++ b/packages/rum-core/src/browser/performanceObservable.spec.ts @@ -1,13 +1,12 @@ import type { Subscription } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../domain/configuration' -import { createPerformanceEntry, mockPerformanceObserver } from '../../test' +import { createPerformanceEntry, mockPerformanceObserver, mockRumConfiguration } from '../../test' import { RumPerformanceEntryType, createPerformanceObservable } from './performanceObservable' describe('performanceObservable', () => { - let configuration: RumConfiguration let performanceSubscription: Subscription | undefined + const configuration = mockRumConfiguration({ isIntakeUrl: (url: string) => url === forbiddenUrl }) const forbiddenUrl = 'https://forbidden.url' const allowedUrl = 'https://allowed.url' let observableCallback: jasmine.Spy @@ -18,7 +17,6 @@ describe('performanceObservable', () => { pending('PerformanceObserver not supported') } observableCallback = jasmine.createSpy() - configuration = { isIntakeUrl: (url: string) => url === forbiddenUrl } as unknown as RumConfiguration clock = mockClock() }) diff --git a/packages/rum-core/src/browser/scroll.spec.ts b/packages/rum-core/src/browser/scroll.spec.ts index 3a69cd5c3d..1f27f14fbe 100644 --- a/packages/rum-core/src/browser/scroll.spec.ts +++ b/packages/rum-core/src/browser/scroll.spec.ts @@ -1,10 +1,10 @@ import { addEventListener, DOM_EVENT, isIE } from '@datadog/browser-core' -import type { RumConfiguration } from '../domain/configuration' +import { mockRumConfiguration } from '../../test' import { getScrollX, getScrollY } from './scroll' describe('scroll', () => { let shouldWaitForWindowScrollEvent: boolean - let configuration: RumConfiguration + const configuration = mockRumConfiguration() const addVerticalScrollBar = () => { document.body.style.setProperty('margin-bottom', '5000px') } @@ -13,7 +13,6 @@ describe('scroll', () => { if (isIE()) { pending('IE not supported') } - configuration = {} as RumConfiguration shouldWaitForWindowScrollEvent = false }) diff --git a/packages/rum-core/src/browser/viewportObservable.spec.ts b/packages/rum-core/src/browser/viewportObservable.spec.ts index bec1531f49..baa5ba3234 100644 --- a/packages/rum-core/src/browser/viewportObservable.spec.ts +++ b/packages/rum-core/src/browser/viewportObservable.spec.ts @@ -1,7 +1,7 @@ import type { Subscription } from '@datadog/browser-core/src/tools/observable' import type { Clock } from '@datadog/browser-core/test' import { mockClock, createNewEvent } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../domain/configuration' +import { mockRumConfiguration } from '../../test' import type { ViewportDimension } from './viewportObservable' import { getViewportDimension, initViewportObservable } from './viewportObservable' @@ -9,10 +9,9 @@ describe('viewportObservable', () => { let viewportSubscription: Subscription let viewportDimension: ViewportDimension let clock: Clock - let configuration: RumConfiguration + const configuration = mockRumConfiguration() beforeEach(() => { - configuration = {} as RumConfiguration viewportSubscription = initViewportObservable(configuration).subscribe((dimension) => { viewportDimension = dimension }) diff --git a/packages/rum-core/src/domain/action/actionCollection.spec.ts b/packages/rum-core/src/domain/action/actionCollection.spec.ts index 116b37b39d..1968c75605 100644 --- a/packages/rum-core/src/domain/action/actionCollection.spec.ts +++ b/packages/rum-core/src/domain/action/actionCollection.spec.ts @@ -1,8 +1,8 @@ import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' import { Observable } from '@datadog/browser-core' import { createNewEvent } from '@datadog/browser-core/test' -import type { RawRumActionEvent, RawRumEventCollectedData, RumConfiguration } from '@datadog/browser-rum-core' -import { collectAndValidateRawRumEvents, mockPageStateHistory } from '../../../test' +import type { RawRumActionEvent, RawRumEventCollectedData } from '@datadog/browser-rum-core' +import { collectAndValidateRawRumEvents, mockPageStateHistory, mockRumConfiguration } from '../../../test' import type { RawRumEvent } from '../../rawRumEvent.types' import { RumEventType, ActionType } from '../../rawRumEvent.types' import { LifeCycle, LifeCycleEventType } from '../lifeCycle' @@ -16,10 +16,14 @@ describe('actionCollection', () => { let rawRumEvents: Array> beforeEach(() => { - const configuration = {} as RumConfiguration const domMutationObservable = new Observable() - ;({ addAction } = startActionCollection(lifeCycle, domMutationObservable, configuration, basePageStateHistory)) + ;({ addAction } = startActionCollection( + lifeCycle, + domMutationObservable, + mockRumConfiguration(), + basePageStateHistory + )) rawRumEvents = collectAndValidateRawRumEvents(lifeCycle) }) diff --git a/packages/rum-core/src/domain/action/getActionNameFromElement.spec.ts b/packages/rum-core/src/domain/action/getActionNameFromElement.spec.ts index 5017702347..d56fb8c905 100644 --- a/packages/rum-core/src/domain/action/getActionNameFromElement.spec.ts +++ b/packages/rum-core/src/domain/action/getActionNameFromElement.spec.ts @@ -1,9 +1,8 @@ -import { appendElement } from '../../../test' +import { appendElement, mockRumConfiguration } from '../../../test' import { NodePrivacyLevel } from '../privacy' -import type { RumConfiguration } from '../configuration' import { getActionNameFromElement } from './getActionNameFromElement' -const defaultConfiguration = {} as RumConfiguration +const defaultConfiguration = mockRumConfiguration() describe('getActionNameFromElement', () => { it('extracts the textual content of an element', () => { diff --git a/packages/rum-core/src/domain/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/action/listenActionEvents.spec.ts index f920c831c6..504ae26610 100644 --- a/packages/rum-core/src/domain/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/action/listenActionEvents.spec.ts @@ -1,5 +1,5 @@ import { createNewEvent } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../configuration' +import { mockRumConfiguration } from '../../../test' import type { ActionEventsHooks } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' @@ -9,15 +9,13 @@ describe('listenActionEvents', () => { onPointerDown: jasmine.Spy['onPointerDown']> } let stopListenEvents: () => void - let configuration: RumConfiguration beforeEach(() => { - configuration = {} as RumConfiguration actionEventsHooks = { onPointerUp: jasmine.createSpy(), onPointerDown: jasmine.createSpy().and.returnValue({}), } - ;({ stop: stopListenEvents } = listenActionEvents(configuration, actionEventsHooks)) + ;({ stop: stopListenEvents } = listenActionEvents(mockRumConfiguration(), actionEventsHooks)) }) afterEach(() => { diff --git a/packages/rum-core/src/domain/action/trackClickActions.spec.ts b/packages/rum-core/src/domain/action/trackClickActions.spec.ts index 736b6c8ae0..436f244708 100644 --- a/packages/rum-core/src/domain/action/trackClickActions.spec.ts +++ b/packages/rum-core/src/domain/action/trackClickActions.spec.ts @@ -9,7 +9,7 @@ import { } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { createNewEvent, mockClock } from '@datadog/browser-core/test' -import { createFakeClick } from '../../../test' +import { createFakeClick, mockRumConfiguration } from '../../../test' import { RumEventType, ActionType, FrustrationType } from '../../rawRumEvent.types' import type { RumEvent } from '../../rumEvent.types' import { LifeCycle, LifeCycleEventType } from '../lifeCycle' @@ -57,7 +57,7 @@ describe('trackClickActions', () => { const trackClickActionsResult = trackClickActions( lifeCycle, domMutationObservable, - partialConfig as RumConfiguration + mockRumConfiguration(partialConfig) ) findActionId = trackClickActionsResult.actionContexts.findActionId diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index 1b3da8eaa0..6edb89f50e 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -1,5 +1,6 @@ import type { ClocksState, RelativeTime, TimeStamp } from '@datadog/browser-core' import { ErrorSource, ExperimentalFeature, ONE_MINUTE, display } from '@datadog/browser-core' +import type { Clock } from '@datadog/browser-core/test' import { mockEventBridge, cleanupSyntheticsWorkerValues, @@ -7,86 +8,41 @@ import { mockExperimentalFeatures, setNavigatorOnLine, setNavigatorConnection, + registerCleanupTask, + mockClock, } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../test' -import { createRumSessionManagerMock, setup, createRawRumEvent } from '../../test' +import { + createRumSessionManagerMock, + createRawRumEvent, + mockRumConfiguration, + mockUrlContexts, + mockActionContexts, + mockDisplayContext, + mockViewContexts, +} from '../../test' import type { RumEventDomainContext } from '../domainContext.types' import type { RawRumActionEvent, RawRumEvent } from '../rawRumEvent.types' import { RumEventType } from '../rawRumEvent.types' import type { RumActionEvent, RumErrorEvent, RumEvent, RumResourceEvent } from '../rumEvent.types' import { startRumAssembly } from './assembly' -import type { LifeCycle, RawRumEventCollectedData } from './lifeCycle' -import { LifeCycleEventType } from './lifeCycle' +import type { RawRumEventCollectedData } from './lifeCycle' +import { LifeCycle, LifeCycleEventType } from './lifeCycle' import type { RumConfiguration } from './configuration' -import type { ViewContext } from './contexts/viewContexts' import type { CommonContext } from './contexts/commonContext' import type { CiVisibilityContext } from './contexts/ciVisibilityContext' +import type { RumSessionManager } from './rumSessionManager' +import type { ViewContexts } from './contexts/viewContexts' describe('rum assembly', () => { - let setupBuilder: TestSetupBuilder - let commonContext: CommonContext - let serverRumEvents: RumEvent[] - let extraConfigurationOptions: Partial = {} - let findView: () => ViewContext - let reportErrorSpy: jasmine.Spy - let ciVisibilityContext: { test_execution_id: string } | undefined - - beforeEach(() => { - findView = () => ({ - id: '7890', - name: 'view name', - startClocks: {} as ClocksState, - }) - reportErrorSpy = jasmine.createSpy('reportError') - commonContext = { - context: {}, - user: {}, - hasReplay: undefined, - } - ciVisibilityContext = undefined - - setupBuilder = setup() - .withViewContexts({ - findView: () => findView(), - }) - .withActionContexts({ - findActionId: () => '7890', - }) - .beforeBuild( - ({ configuration, lifeCycle, sessionManager, viewContexts, urlContexts, actionContexts, displayContext }) => { - serverRumEvents = [] - lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, (serverRumEvent) => - serverRumEvents.push(serverRumEvent) - ) - startRumAssembly( - { ...configuration, ...extraConfigurationOptions }, - lifeCycle, - sessionManager, - viewContexts, - urlContexts, - actionContexts, - displayContext, - { get: () => ciVisibilityContext } as CiVisibilityContext, - () => commonContext, - reportErrorSpy - ) - } - ) - }) - - afterEach(() => { - cleanupSyntheticsWorkerValues() - }) - describe('beforeSend', () => { describe('fields modification', () => { describe('modifiable fields', () => { it('should allow modification', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => (event.view.url = 'modified'), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { view: { url: '/path?foo=bar' } }), @@ -96,11 +52,11 @@ describe('rum assembly', () => { }) it('should allow addition', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => (event.view.name = 'added'), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { view: { url: '/path?foo=bar' } }), @@ -111,11 +67,11 @@ describe('rum assembly', () => { describe('field resource.graphql on Resource events', () => { it('by default, it should not be modifiable', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => (event.resource!.graphql = { operationType: 'query' }), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.RESOURCE, { resource: { url: '/path?foo=bar' } }), @@ -127,11 +83,11 @@ describe('rum assembly', () => { it('with the writable_resource_graphql experimental flag is set, it should be modifiable', () => { mockExperimentalFeatures([ExperimentalFeature.WRITABLE_RESOURCE_GRAPHQL]) - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => (event.resource!.graphql = { operationType: 'query' }), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.RESOURCE, { resource: { url: '/path?foo=bar' } }), @@ -144,13 +100,13 @@ describe('rum assembly', () => { describe('context field', () => { it('should allow modification on context field for events other than views', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context.foo = 'bar' }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -160,13 +116,13 @@ describe('rum assembly', () => { }) it('should allow replacing the context field for events other than views', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { - event.context = { foo: 'bar' } + event.context.foo = 'bar' }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -176,13 +132,13 @@ describe('rum assembly', () => { }) it('should empty the context field if set to null', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context = null }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -193,13 +149,13 @@ describe('rum assembly', () => { }) it('should empty the context field if set to undefined', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context = undefined }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -210,13 +166,13 @@ describe('rum assembly', () => { }) it('should empty the context field if deleted', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { delete event.context }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -227,13 +183,13 @@ describe('rum assembly', () => { }) it('should define the context field even if the global context is empty', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { expect(event.context).toEqual({}) }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -242,13 +198,13 @@ describe('rum assembly', () => { it('should accept modification on context field for view events', () => { mockExperimentalFeatures([ExperimentalFeature.VIEW_SPECIFIC_CONTEXT]) - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context.foo = 'bar' }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -258,13 +214,13 @@ describe('rum assembly', () => { }) it('should reject modification on context field for view events', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context.foo = 'bar' }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -274,13 +230,13 @@ describe('rum assembly', () => { }) it('should reject replacing the context field to non-object', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.context = 1 }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), @@ -293,11 +249,11 @@ describe('rum assembly', () => { describe('allowed customer provided field', () => { it('should allow modification of the error fingerprint', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => (event.error.fingerprint = 'my_fingerprint'), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ERROR), @@ -308,11 +264,11 @@ describe('rum assembly', () => { }) it('should reject modification of field not sensitive, context or customer provided', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event: RumEvent) => ((event.view as any).id = 'modified'), - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { @@ -324,13 +280,13 @@ describe('rum assembly', () => { }) it('should not allow to add a sensitive field on the wrong event type', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: (event) => { event.error = { message: 'added' } }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -342,11 +298,11 @@ describe('rum assembly', () => { describe('events dismission', () => { it('should allow dismissing events other than views', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: () => false, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION, { @@ -376,11 +332,11 @@ describe('rum assembly', () => { }) it('should not allow dismissing view events', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: () => false, - }) - .build() + }, + }) const displaySpy = spyOn(display, 'warn') notifyRawRumEvent(lifeCycle, { @@ -395,11 +351,11 @@ describe('rum assembly', () => { }) it('should not dismiss when true is returned', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: () => true, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION, { @@ -411,11 +367,11 @@ describe('rum assembly', () => { }) it('should not dismiss when undefined is returned', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { beforeSend: () => undefined, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION, { @@ -429,7 +385,7 @@ describe('rum assembly', () => { describe('rum context', () => { it('should be merged with event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW, undefined), }) @@ -441,7 +397,7 @@ describe('rum assembly', () => { }) it('should be overwritten by event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW, { date: 10 }), }) @@ -452,7 +408,7 @@ describe('rum assembly', () => { describe('priority of rum context', () => { it('should prioritize view customer context over global context', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.context = { foo: 'bar' } notifyRawRumEvent(lifeCycle, { customerContext: { foo: 'baz' }, @@ -463,13 +419,15 @@ describe('rum assembly', () => { }) it('should prioritize child customer context over inherited view context', () => { - const { lifeCycle } = setupBuilder.build() - findView = () => ({ + const { lifeCycle, serverRumEvents, setFindView } = setupAssemblyTestWithDefaults() + const newFindView = () => ({ id: '7890', name: 'view name', startClocks: {} as ClocksState, customerContext: { foo: 'bar' }, }) + setFindView(newFindView) + notifyRawRumEvent(lifeCycle, { customerContext: { foo: 'baz' }, rawRumEvent: createRawRumEvent(RumEventType.ACTION), @@ -481,7 +439,7 @@ describe('rum assembly', () => { describe('rum global context', () => { it('should be merged with event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.context = { bar: 'foo' } notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -491,7 +449,7 @@ describe('rum assembly', () => { }) it('should not be included if empty', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.context = {} notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -501,7 +459,7 @@ describe('rum assembly', () => { }) it('should ignore subsequent context mutation', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.context = { bar: 'foo', baz: 'foz' } notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -516,7 +474,7 @@ describe('rum assembly', () => { }) it('should ignore the current global context when a saved global context is provided', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.context = { replacedContext: 'b', addedContext: 'x' } notifyRawRumEvent(lifeCycle, { @@ -535,7 +493,7 @@ describe('rum assembly', () => { describe('rum user', () => { it('should be included in event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.user = { id: 'foo' } notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -545,7 +503,7 @@ describe('rum assembly', () => { }) it('should not be included if empty', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.user = {} notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -555,7 +513,7 @@ describe('rum assembly', () => { }) it('should ignore the current user when a saved common context user is provided', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.user = { replacedAttribute: 'b', addedAttribute: 'x' } notifyRawRumEvent(lifeCycle, { @@ -574,7 +532,7 @@ describe('rum assembly', () => { describe('customer context', () => { it('should be merged with event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { customerContext: { foo: 'bar' }, rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -586,33 +544,33 @@ describe('rum assembly', () => { describe('action context', () => { it('should be added on some event categories', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() ;[RumEventType.RESOURCE, RumEventType.LONG_TASK, RumEventType.ERROR].forEach((category) => { notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(category), }) expect(serverRumEvents[0].action).toEqual({ id: '7890' }) - serverRumEvents = [] + serverRumEvents.length = 0 }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) expect(serverRumEvents[0].action).not.toBeDefined() - serverRumEvents = [] + serverRumEvents.length = 0 const generatedRawRumActionEvent = createRawRumEvent(RumEventType.ACTION) as RawRumActionEvent notifyRawRumEvent(lifeCycle, { rawRumEvent: generatedRawRumActionEvent, }) expect((serverRumEvents[0] as RumActionEvent).action.id).toEqual(generatedRawRumActionEvent.action.id) - serverRumEvents = [] + serverRumEvents.length = 0 }) }) describe('view context', () => { it('should be merged with event attributes', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) @@ -625,13 +583,15 @@ describe('rum assembly', () => { }) it('child event should have view customer context', () => { - const { lifeCycle } = setupBuilder.build() - findView = () => ({ + const { lifeCycle, serverRumEvents, setFindView } = setupAssemblyTestWithDefaults() + const newFindView = () => ({ id: '7890', name: 'view name', startClocks: {} as ClocksState, customerContext: { foo: 'bar' }, }) + setFindView(newFindView) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) @@ -640,12 +600,12 @@ describe('rum assembly', () => { }) describe('service and version', () => { - beforeEach(() => { - extraConfigurationOptions = { service: 'default service', version: 'default version' } - }) + const extraConfigurationOptions = { service: 'default service', version: 'default version' } it('should come from the init configuration by default', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: extraConfigurationOptions, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), @@ -655,8 +615,18 @@ describe('rum assembly', () => { }) it('should be overridden by the view context', () => { - const { lifeCycle } = setupBuilder.build() - findView = () => ({ service: 'new service', version: 'new version', id: '1234', startClocks: {} as ClocksState }) + const { lifeCycle, serverRumEvents, setFindView } = setupAssemblyTestWithDefaults({ + partialConfiguration: extraConfigurationOptions, + }) + + const newFindView = () => ({ + service: 'new service', + version: 'new version', + id: '1234', + startClocks: {} as ClocksState, + }) + setFindView(newFindView) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) @@ -666,16 +636,17 @@ describe('rum assembly', () => { describe('fields service and version', () => { it('it should be modifiable', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { + ...extraConfigurationOptions, beforeSend: (event) => { event.service = 'bar' event.version = '0.2.0' return true }, - }) - .build() + }, + }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.RESOURCE, { resource: { url: '/path?foo=bar' } }), @@ -689,18 +660,18 @@ describe('rum assembly', () => { describe('url context', () => { it('should be merged with event attributes', () => { - const { lifeCycle, fakeLocation } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) - expect(serverRumEvents[0].view.url).toBe(fakeLocation.href!) + expect(serverRumEvents[0].view.url).toBe(location.href) expect(serverRumEvents[0].view.referrer).toBe(document.referrer) }) }) describe('event generation condition', () => { it('when tracked, it should generate event', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -708,7 +679,9 @@ describe('rum assembly', () => { }) it('when not tracked, it should not generate event', () => { - const { lifeCycle } = setupBuilder.withSessionManager(createRumSessionManagerMock().setNotTracked()).build() + const sessionManager = createRumSessionManagerMock().setNotTracked() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ sessionManager }) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -716,22 +689,22 @@ describe('rum assembly', () => { }) it('should get session state from event start', () => { - const rumSessionManager = createRumSessionManagerMock() - spyOn(rumSessionManager, 'findTrackedSession').and.callThrough() + const sessionManager = createRumSessionManagerMock() + spyOn(sessionManager, 'findTrackedSession').and.callThrough() + const { lifeCycle } = setupAssemblyTestWithDefaults({ sessionManager }) - const { lifeCycle } = setupBuilder.withSessionManager(rumSessionManager).build() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), startTime: 123 as RelativeTime, }) - expect(rumSessionManager.findTrackedSession).toHaveBeenCalledWith(123 as RelativeTime) + expect(sessionManager.findTrackedSession).toHaveBeenCalledWith(123 as RelativeTime) }) }) describe('session context', () => { it('should include the session type and id', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -747,7 +720,7 @@ describe('rum assembly', () => { it('should detect synthetics sessions based on synthetics worker values', () => { mockSyntheticsWorkerValues() - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -756,9 +729,9 @@ describe('rum assembly', () => { }) it('should detect ci visibility tests', () => { - ciVisibilityContext = { test_execution_id: 'traceId' } + const ciVisibilityContext = { test_execution_id: 'traceId' } + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ ciVisibilityContext }) - const { lifeCycle } = setupBuilder.build() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -767,7 +740,7 @@ describe('rum assembly', () => { }) it('should set the session.has_replay attribute if it is defined in the common context', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.hasReplay = true notifyRawRumEvent(lifeCycle, { @@ -777,7 +750,7 @@ describe('rum assembly', () => { }) it('should not use commonContext.hasReplay on view events', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents, commonContext } = setupAssemblyTestWithDefaults() commonContext.hasReplay = true notifyRawRumEvent(lifeCycle, { @@ -787,9 +760,8 @@ describe('rum assembly', () => { }) it('should set sampled_for_replay on view events when tracked with replay', () => { - const { lifeCycle } = setupBuilder - .withSessionManager(createRumSessionManagerMock().setTrackedWithSessionReplay()) - .build() + const sessionManager = createRumSessionManagerMock().setTrackedWithSessionReplay() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ sessionManager }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -799,9 +771,8 @@ describe('rum assembly', () => { }) it('should set sampled_for_replay on view events when tracked without replay', () => { - const { lifeCycle } = setupBuilder - .withSessionManager(createRumSessionManagerMock().setTrackedWithoutSessionReplay()) - .build() + const sessionManager = createRumSessionManagerMock().setTrackedWithoutSessionReplay() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ sessionManager }) notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), @@ -811,7 +782,7 @@ describe('rum assembly', () => { }) it('should not set sampled_for_replay on other events', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ERROR), @@ -823,7 +794,7 @@ describe('rum assembly', () => { describe('configuration context', () => { it('should include the configured sample rates', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) @@ -834,12 +805,13 @@ describe('rum assembly', () => { }) it('should round sample rates', () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { sessionSampleRate: 1.2341, sessionReplaySampleRate: 6.7891, - }) - .build() + }, + }) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), }) @@ -853,8 +825,8 @@ describe('rum assembly', () => { describe('synthetics context', () => { it('includes the synthetics context', () => { mockSyntheticsWorkerValues() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() - const { lifeCycle } = setupBuilder.build() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -865,7 +837,7 @@ describe('rum assembly', () => { describe('if event bridge detected', () => { it('includes the browser sdk version', () => { - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW) }) mockEventBridge() @@ -879,9 +851,9 @@ describe('rum assembly', () => { describe('ci visibility context', () => { it('includes the ci visibility context', () => { - ciVisibilityContext = { test_execution_id: 'traceId' } + const ciVisibilityContext = { test_execution_id: 'traceId' } + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ ciVisibilityContext }) - const { lifeCycle } = setupBuilder.build() notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), }) @@ -895,7 +867,7 @@ describe('rum assembly', () => { setNavigatorOnLine(true) setNavigatorConnection({ effectiveType: '2g' }) - const { lifeCycle } = setupBuilder.build() + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults() const rawRumEvent = createRawRumEvent(RumEventType.VIEW) notifyRawRumEvent(lifeCycle, { rawRumEvent }) @@ -922,7 +894,10 @@ describe('rum assembly', () => { ].forEach(({ eventType, message }) => { describe(`${eventType} events limitation`, () => { it(`stops sending ${eventType} events when reaching the limit`, () => { - const { lifeCycle } = setupBuilder.withConfiguration({ eventRateLimiterThreshold: 1 }).build() + const { lifeCycle, serverRumEvents, reportErrorSpy } = setupAssemblyTestWithDefaults({ + partialConfiguration: { eventRateLimiterThreshold: 1 }, + }) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(eventType, { date: 100 as TimeStamp }), }) @@ -942,16 +917,17 @@ describe('rum assembly', () => { }) it(`does not take discarded ${eventType} events into account`, () => { - const { lifeCycle } = setupBuilder - .withConfiguration({ + const { lifeCycle, serverRumEvents, reportErrorSpy } = setupAssemblyTestWithDefaults({ + partialConfiguration: { eventRateLimiterThreshold: 1, beforeSend: (event) => { if (event.type === eventType && event.date === 100) { return false } }, - }) - .build() + }, + }) + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(eventType, { date: 100 as TimeStamp }), }) @@ -969,25 +945,33 @@ describe('rum assembly', () => { expect(reportErrorSpy).not.toHaveBeenCalled() }) - it(`allows to send new ${eventType} events after a minute`, () => { - const { lifeCycle, clock } = setupBuilder - .withFakeClock() - .withConfiguration({ eventRateLimiterThreshold: 1 }) - .build() - notifyRawRumEvent(lifeCycle, { - rawRumEvent: createRawRumEvent(eventType, { date: 100 as TimeStamp }), - }) - notifyRawRumEvent(lifeCycle, { - rawRumEvent: createRawRumEvent(eventType, { date: 200 as TimeStamp }), - }) - clock.tick(ONE_MINUTE) - notifyRawRumEvent(lifeCycle, { - rawRumEvent: createRawRumEvent(eventType, { date: 300 as TimeStamp }), + describe('when clock ticks with one minute', () => { + let clock: Clock + beforeEach(() => { + clock = mockClock() + registerCleanupTask(() => clock.cleanup()) }) - expect(serverRumEvents.length).toBe(2) - expect(serverRumEvents[0].date).toBe(100) - expect(serverRumEvents[1].date).toBe(300) + it(`allows to send new ${eventType} events after a minute`, () => { + const { lifeCycle, serverRumEvents } = setupAssemblyTestWithDefaults({ + partialConfiguration: { eventRateLimiterThreshold: 1 }, + }) + + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(eventType, { date: 100 as TimeStamp }), + }) + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(eventType, { date: 200 as TimeStamp }), + }) + clock.tick(ONE_MINUTE) + notifyRawRumEvent(lifeCycle, { + rawRumEvent: createRawRumEvent(eventType, { date: 300 as TimeStamp }), + }) + + expect(serverRumEvents.length).toBe(2) + expect(serverRumEvents[0].date).toBe(100) + expect(serverRumEvents[1].date).toBe(300) + }) }) }) }) @@ -1005,3 +989,55 @@ function notifyRawRumEvent( } lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, fullData) } + +interface AssemblyTestParams { + partialConfiguration?: Partial + sessionManager?: RumSessionManager + ciVisibilityContext?: Record +} + +function setupAssemblyTestWithDefaults({ + partialConfiguration, + sessionManager, + ciVisibilityContext, +}: AssemblyTestParams = {}) { + const lifeCycle = new LifeCycle() + const reportErrorSpy = jasmine.createSpy('reportError') + const rumSessionManager = sessionManager ?? createRumSessionManagerMock().setId('1234') + const commonContext = { + context: {}, + user: {}, + hasReplay: undefined, + } as CommonContext + + const serverRumEvents: RumEvent[] = [] + const subscription = lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, (serverRumEvent) => { + serverRumEvents.push(serverRumEvent) + }) + + let findView: ViewContexts['findView'] = () => ({ id: '7890', name: 'view name', startClocks: {} as ClocksState }) + + const setFindView = (newFindView: ViewContexts['findView']) => { + findView = newFindView + } + + startRumAssembly( + mockRumConfiguration(partialConfiguration), + lifeCycle, + rumSessionManager, + { ...mockViewContexts(), findView: () => findView() }, + mockUrlContexts(), + mockActionContexts(), + mockDisplayContext(), + { get: () => ciVisibilityContext } as CiVisibilityContext, + () => commonContext, + reportErrorSpy + ) + + registerCleanupTask(() => { + subscription.unsubscribe() + cleanupSyntheticsWorkerValues() + }) + + return { lifeCycle, reportErrorSpy, serverRumEvents, commonContext, setFindView } +} diff --git a/packages/rum-core/src/domain/contexts/displayContext.spec.ts b/packages/rum-core/src/domain/contexts/displayContext.spec.ts index 0b9d2b8b03..34a50c1419 100644 --- a/packages/rum-core/src/domain/contexts/displayContext.spec.ts +++ b/packages/rum-core/src/domain/contexts/displayContext.spec.ts @@ -1,14 +1,12 @@ -import type { RumConfiguration } from '../configuration' +import { mockRumConfiguration } from '../../../test' import type { DisplayContext } from './displayContext' import { startDisplayContext } from './displayContext' describe('displayContext', () => { - let configuration: RumConfiguration let displayContext: DisplayContext beforeEach(() => { - configuration = {} as RumConfiguration - displayContext = startDisplayContext(configuration) + displayContext = startDisplayContext(mockRumConfiguration()) }) afterEach(() => { diff --git a/packages/rum-core/src/domain/contexts/internalContext.spec.ts b/packages/rum-core/src/domain/contexts/internalContext.spec.ts index 630086fe99..54bcbe857a 100644 --- a/packages/rum-core/src/domain/contexts/internalContext.spec.ts +++ b/packages/rum-core/src/domain/contexts/internalContext.spec.ts @@ -1,6 +1,6 @@ -import type { RelativeTime } from '@datadog/browser-core' -import { createRumSessionManagerMock, setup } from '../../../test' -import type { TestSetupBuilder } from '../../../test' +import { noop, type RelativeTime } from '@datadog/browser-core' +import { buildLocation } from '@datadog/browser-core/test' +import { createRumSessionManagerMock } from '../../../test' import type { ActionContexts } from '../action/actionCollection' import type { RumSessionManager } from '../rumSessionManager' import { startInternalContext } from './internalContext' @@ -8,36 +8,43 @@ import type { ViewContexts } from './viewContexts' import type { UrlContexts } from './urlContexts' describe('internal context', () => { - let setupBuilder: TestSetupBuilder - let viewContextsStub: Partial - let actionContextsStub: ActionContexts let findUrlSpy: jasmine.Spy let findSessionSpy: jasmine.Spy - let internalContext: ReturnType + let fakeLocation: Location + let viewContexts: ViewContexts + let actionContexts: ActionContexts - beforeEach(() => { - viewContextsStub = { + function setupInternalContext(sessionManager: RumSessionManager) { + viewContexts = { findView: jasmine.createSpy('findView').and.returnValue({ id: 'abcde', name: 'foo', }), + stop: noop, } - actionContextsStub = { + + actionContexts = { findActionId: jasmine.createSpy('findActionId').and.returnValue('7890'), } - setupBuilder = setup() - .withSessionManager(createRumSessionManagerMock().setId('456')) - .withViewContexts(viewContextsStub) - .withActionContexts(actionContextsStub) - .beforeBuild(({ applicationId, sessionManager, viewContexts, urlContexts, actionContexts }) => { - findUrlSpy = spyOn(urlContexts, 'findUrl').and.callThrough() - findSessionSpy = spyOn(sessionManager, 'findTrackedSession').and.callThrough() - internalContext = startInternalContext(applicationId, sessionManager, viewContexts, actionContexts, urlContexts) - }) - }) + + fakeLocation = buildLocation('/foo') + + const urlContexts: UrlContexts = { + findUrl: () => ({ + url: fakeLocation.href, + referrer: document.referrer, + }), + stop: noop, + } + findSessionSpy = spyOn(sessionManager, 'findTrackedSession').and.callThrough() + findUrlSpy = spyOn(urlContexts, 'findUrl').and.callThrough() + + return startInternalContext('appId', sessionManager, viewContexts, actionContexts, urlContexts) + } it('should return current internal context', () => { - const { fakeLocation } = setupBuilder.build() + const sessionManager = createRumSessionManagerMock().setId('456') + const internalContext = setupInternalContext(sessionManager) expect(internalContext.get()).toEqual({ application_id: 'appId', @@ -49,23 +56,25 @@ describe('internal context', () => { id: 'abcde', name: 'foo', referrer: document.referrer, - url: fakeLocation.href!, + url: fakeLocation.href, }, }) }) it("should return undefined if the session isn't tracked", () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setNotTracked()).build() + const sessionManager = createRumSessionManagerMock().setNotTracked() + const internalContext = setupInternalContext(sessionManager) expect(internalContext.get()).toEqual(undefined) }) it('should return internal context corresponding to startTime', () => { - setupBuilder.build() + const sessionManager = createRumSessionManagerMock().setId('456') + const internalContext = setupInternalContext(sessionManager) internalContext.get(123) - expect(viewContextsStub.findView).toHaveBeenCalledWith(123) - expect(actionContextsStub.findActionId).toHaveBeenCalledWith(123 as RelativeTime) + expect(viewContexts.findView).toHaveBeenCalledWith(123 as RelativeTime) + expect(actionContexts.findActionId).toHaveBeenCalledWith(123 as RelativeTime) expect(findUrlSpy).toHaveBeenCalledWith(123 as RelativeTime) expect(findSessionSpy).toHaveBeenCalledWith(123 as RelativeTime) }) diff --git a/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts b/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts index d040fdedf4..63b66825a6 100644 --- a/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts +++ b/packages/rum-core/src/domain/contexts/pageStateHistory.spec.ts @@ -1,17 +1,16 @@ import type { RelativeTime, ServerDuration, Duration } from '@datadog/browser-core' import type { Clock } from '../../../../core/test' import { mockClock, registerCleanupTask } from '../../../../core/test' -import type { RumConfiguration } from '../configuration' +import { mockRumConfiguration } from '../../../test' import type { PageStateHistory } from './pageStateHistory' import { PageState, startPageStateHistory } from './pageStateHistory' describe('pageStateHistory', () => { let pageStateHistory: PageStateHistory let clock: Clock - let configuration: RumConfiguration + const configuration = mockRumConfiguration() beforeEach(() => { - configuration = {} as RumConfiguration clock = mockClock() pageStateHistory = startPageStateHistory(configuration) registerCleanupTask(pageStateHistory.stop) diff --git a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts index 8f4f9c3f75..a5f8a46d1e 100644 --- a/packages/rum-core/src/domain/contexts/urlContexts.spec.ts +++ b/packages/rum-core/src/domain/contexts/urlContexts.spec.ts @@ -1,43 +1,41 @@ +import { mockClock, registerCleanupTask, type Clock } from '@datadog/browser-core/test' import type { RelativeTime } from '@datadog/browser-core' import { relativeToClocks } from '@datadog/browser-core' -import type { TestSetupBuilder } from '../../../test' -import { setup } from '../../../test' -import { LifeCycleEventType } from '../lifeCycle' +import { setupLocationObserver } from '../../../test' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' import type { ViewCreatedEvent, ViewEndedEvent } from '../view/trackViews' -import type { UrlContexts } from './urlContexts' -import { startUrlContexts } from './urlContexts' +import { startUrlContexts, type UrlContexts } from './urlContexts' describe('urlContexts', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let changeLocation: (to: string) => void let urlContexts: UrlContexts + let clock: Clock beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('http://fake-url.com') - .withFakeClock() - .beforeBuild(({ lifeCycle, locationChangeObservable, location }) => { - urlContexts = startUrlContexts(lifeCycle, locationChangeObservable, location) - return urlContexts - }) + clock = mockClock() + const setupResult = setupLocationObserver('http://fake-url.com') + + changeLocation = setupResult.changeLocation + urlContexts = startUrlContexts(lifeCycle, setupResult.locationChangeObservable, setupResult.fakeLocation) + + registerCleanupTask(() => { + urlContexts.stop() + clock.cleanup() + }) }) it('should return undefined before the initial view', () => { - setupBuilder.build() - expect(urlContexts.findUrl()).toBeUndefined() }) it('should not create url context on location change before the initial view', () => { - const { changeLocation } = setupBuilder.build() - changeLocation('/foo') expect(urlContexts.findUrl()).toBeUndefined() }) it('should return current url and document referrer for initial view', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -48,8 +46,6 @@ describe('urlContexts', () => { }) it('should update url context on location change', () => { - const { lifeCycle, changeLocation } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -61,8 +57,6 @@ describe('urlContexts', () => { }) it('should update url context on new view', () => { - const { lifeCycle, changeLocation } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -80,8 +74,6 @@ describe('urlContexts', () => { }) it('should return the url context corresponding to the start time', () => { - const { lifeCycle, changeLocation, clock } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -130,8 +122,6 @@ describe('urlContexts', () => { * (which seems unlikely) and this event would anyway be rejected by lack of view id */ it('should return undefined when no current view', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) diff --git a/packages/rum-core/src/domain/error/trackReportError.spec.ts b/packages/rum-core/src/domain/error/trackReportError.spec.ts index a63fe930dc..89c93f2465 100644 --- a/packages/rum-core/src/domain/error/trackReportError.spec.ts +++ b/packages/rum-core/src/domain/error/trackReportError.spec.ts @@ -8,6 +8,7 @@ import { mockCspEventListener, mockReportingObserver, } from '@datadog/browser-core/test' +import { mockRumConfiguration } from '../../../test' import type { RumConfiguration } from '../configuration' import { trackReportError } from './trackReportError' @@ -21,7 +22,7 @@ describe('trackReportError', () => { let configuration: RumConfiguration beforeEach(() => { - configuration = {} as RumConfiguration + configuration = mockRumConfiguration() errorObservable = new Observable() notifyLog = jasmine.createSpy('notifyLog') reportingObserver = mockReportingObserver() diff --git a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts index 7fd3b62cd5..d2b0938684 100644 --- a/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts +++ b/packages/rum-core/src/domain/longAnimationFrame/longAnimationFrameCollection.spec.ts @@ -1,10 +1,14 @@ import { type RelativeTime, type ServerDuration } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' -import { collectAndValidateRawRumEvents, createPerformanceEntry, mockPerformanceObserver } from '../../../test' +import { + collectAndValidateRawRumEvents, + createPerformanceEntry, + mockPerformanceObserver, + mockRumConfiguration, +} from '../../../test' import { RumPerformanceEntryType } from '../../browser/performanceObservable' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import { LifeCycle } from '../lifeCycle' -import type { RumConfiguration } from '../configuration' import { startLongAnimationFrameCollection } from './longAnimationFrameCollection' describe('long animation frames collection', () => { @@ -81,11 +85,13 @@ describe('long animation frames collection', () => { function setupLongAnimationFrameCollection() { const lifeCycle = new LifeCycle() - const configuration = {} as RumConfiguration const notifyPerformanceEntries = mockPerformanceObserver().notifyPerformanceEntries const rawRumEvents = collectAndValidateRawRumEvents(lifeCycle) - const { stop: stopLongAnimationFrameCollection } = startLongAnimationFrameCollection(lifeCycle, configuration) + const { stop: stopLongAnimationFrameCollection } = startLongAnimationFrameCollection( + lifeCycle, + mockRumConfiguration() + ) registerCleanupTask(() => { stopLongAnimationFrameCollection() diff --git a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts index 489ecbda6b..84167272d6 100644 --- a/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/longTask/longTaskCollection.spec.ts @@ -1,11 +1,15 @@ import type { RelativeTime, ServerDuration } from '@datadog/browser-core' -import { collectAndValidateRawRumEvents, createPerformanceEntry, mockPerformanceObserver } from '../../../test' +import { + collectAndValidateRawRumEvents, + createPerformanceEntry, + mockPerformanceObserver, + mockRumConfiguration, +} from '../../../test' import { RumPerformanceEntryType } from '../../browser/performanceObservable' import type { RawRumEvent } from '../../rawRumEvent.types' import { RumEventType, RumLongTaskEntryType } from '../../rawRumEvent.types' import type { RawRumEventCollectedData } from '../lifeCycle' import { LifeCycle, LifeCycleEventType } from '../lifeCycle' -import type { RumConfiguration } from '../configuration' import { startLongTaskCollection } from './longTaskCollection' describe('long task collection', () => { @@ -14,7 +18,7 @@ describe('long task collection', () => { function setupLongTaskCollection(trackLongTasks = true) { lifeCycle = new LifeCycle() - startLongTaskCollection(lifeCycle, { trackLongTasks } as RumConfiguration) + startLongTaskCollection(lifeCycle, mockRumConfiguration({ trackLongTasks })) rawRumEvents = collectAndValidateRawRumEvents(lifeCycle) } diff --git a/packages/rum-core/src/domain/requestCollection.spec.ts b/packages/rum-core/src/domain/requestCollection.spec.ts index fa2efe03a0..ac61d5eeca 100644 --- a/packages/rum-core/src/domain/requestCollection.spec.ts +++ b/packages/rum-core/src/domain/requestCollection.spec.ts @@ -2,8 +2,7 @@ import type { Payload } from '@datadog/browser-core' import { isIE, RequestType } from '@datadog/browser-core' import type { MockFetch, MockFetchManager, MockXhrManager } from '@datadog/browser-core/test' import { registerCleanupTask, SPEC_ENDPOINTS, mockFetch, mockXhr, withXhr } from '@datadog/browser-core/test' -import type { RumConfiguration } from './configuration' -import { validateAndBuildRumConfiguration } from './configuration' +import { mockRumConfiguration } from '../../test' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import type { RequestCompleteEvent, RequestStartEvent } from './requestCollection' import { trackFetch, trackXhr } from './requestCollection' @@ -13,7 +12,6 @@ import { clearTracingIfNeeded, createTraceIdentifier } from './tracing/tracer' const DEFAULT_PAYLOAD = {} as Payload describe('collect fetch', () => { - let configuration: RumConfiguration const FAKE_URL = 'http://fake-url/' let fetch: MockFetch let mockFetchManager: MockFetchManager @@ -25,11 +23,7 @@ describe('collect fetch', () => { if (isIE()) { pending('no fetch support') } - configuration = { - ...validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: 'xxx' })!, - ...SPEC_ENDPOINTS, - batchMessagesLimit: 1, - } + const configuration = mockRumConfiguration({ batchMessagesLimit: 1 }) mockFetchManager = mockFetch() startSpy = jasmine.createSpy('requestStart') @@ -186,7 +180,6 @@ describe('collect fetch', () => { }) describe('collect xhr', () => { - let configuration: RumConfiguration let startSpy: jasmine.Spy<(requestStartEvent: RequestStartEvent) => void> let completeSpy: jasmine.Spy<(requestCompleteEvent: RequestCompleteEvent) => void> let mockXhrManager: MockXhrManager @@ -196,11 +189,7 @@ describe('collect xhr', () => { if (isIE()) { pending('no fetch support') } - configuration = { - ...validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: 'xxx' })!, - ...SPEC_ENDPOINTS, - batchMessagesLimit: 1, - } + const configuration = mockRumConfiguration({ batchMessagesLimit: 1 }) mockXhrManager = mockXhr() startSpy = jasmine.createSpy('requestStart') completeSpy = jasmine.createSpy('requestComplete') diff --git a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts index 2b64e2e32a..76316b3563 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts @@ -7,6 +7,7 @@ import { createPerformanceEntry, mockPageStateHistory, mockPerformanceObserver, + mockRumConfiguration, } from '../../../test' import type { RawRumEvent, RawRumResourceEvent } from '../../rawRumEvent.types' import { RumEventType } from '../../rawRumEvent.types' @@ -21,12 +22,7 @@ import { RumPerformanceEntryType } from '../../browser/performanceObservable' import { startResourceCollection } from './resourceCollection' const HANDLING_STACK_REGEX = /^Error: \n\s+at @/ -const baseConfiguration: RumConfiguration = { - ...validateAndBuildRumConfiguration({ - clientToken: 'xxx', - applicationId: 'FAKE_APP_ID', - })!, -} +const baseConfiguration = mockRumConfiguration() const pageStateHistory = mockPageStateHistory() describe('resourceCollection', () => { diff --git a/packages/rum-core/src/domain/resource/resourceUtils.spec.ts b/packages/rum-core/src/domain/resource/resourceUtils.spec.ts index 8f7645cee8..d69aeb86f5 100644 --- a/packages/rum-core/src/domain/resource/resourceUtils.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceUtils.spec.ts @@ -1,8 +1,8 @@ import { ExperimentalFeature, type Duration, type RelativeTime, type ServerDuration } from '@datadog/browser-core' -import { SPEC_ENDPOINTS, mockExperimentalFeatures } from '@datadog/browser-core/test' +import { mockExperimentalFeatures } from '@datadog/browser-core/test' import { RumPerformanceEntryType, type RumPerformanceResourceTiming } from '../../browser/performanceObservable' import type { RumConfiguration } from '../configuration' -import { validateAndBuildRumConfiguration } from '../configuration' +import { mockRumConfiguration } from '../../../test' import { MAX_ATTRIBUTE_VALUE_CHAR_LENGTH, computePerformanceResourceDetails, @@ -293,10 +293,7 @@ describe('shouldTrackResource', () => { let configuration: RumConfiguration beforeEach(() => { - configuration = { - ...validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: 'xxx' })!, - ...SPEC_ENDPOINTS, - } + configuration = mockRumConfiguration() }) it('should exclude requests on intakes endpoints', () => { diff --git a/packages/rum-core/src/domain/resource/retrieveInitialDocumentResourceTiming.spec.ts b/packages/rum-core/src/domain/resource/retrieveInitialDocumentResourceTiming.spec.ts index b836918773..7b54763b5c 100644 --- a/packages/rum-core/src/domain/resource/retrieveInitialDocumentResourceTiming.spec.ts +++ b/packages/rum-core/src/domain/resource/retrieveInitialDocumentResourceTiming.spec.ts @@ -1,9 +1,9 @@ -import type { RumConfiguration } from '../configuration' +import { mockRumConfiguration } from '../../../test' import { retrieveInitialDocumentResourceTiming } from './retrieveInitialDocumentResourceTiming' describe('rum initial document resource', () => { it('creates a resource timing for the initial document', (done) => { - retrieveInitialDocumentResourceTiming({} as RumConfiguration, (timing) => { + retrieveInitialDocumentResourceTiming(mockRumConfiguration(), (timing) => { expect(timing.entryType).toBe('resource') expect(timing.duration).toBeGreaterThan(0) diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index 73e533f44b..e44272667b 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -20,8 +20,8 @@ import { mockClock, registerCleanupTask, } from '@datadog/browser-core/test' +import { mockRumConfiguration } from '../../test' import type { RumConfiguration } from './configuration' -import { validateAndBuildRumConfiguration } from './configuration' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import { @@ -222,14 +222,13 @@ describe('rum session manager', () => { function startRumSessionManagerWithDefaults({ configuration }: { configuration?: Partial } = {}) { return startRumSessionManager( - { - ...validateAndBuildRumConfiguration({ clientToken: 'xxx', applicationId: 'xxx' })!, + mockRumConfiguration({ sessionSampleRate: 50, sessionReplaySampleRate: 50, trackResources: true, trackLongTasks: true, ...configuration, - }, + }), lifeCycle, createTrackingConsentState(TrackingConsent.GRANTED) ) diff --git a/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts b/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts index de8d7e676e..30747cbc34 100644 --- a/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts +++ b/packages/rum-core/src/domain/startCustomerDataTelemetry.spec.ts @@ -8,6 +8,7 @@ import { } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock } from '@datadog/browser-core/test' +import { mockRumConfiguration } from '../../test' import { RumEventType } from '../rawRumEvent.types' import type { RumEvent } from '../rumEvent.types' import { LifeCycle, LifeCycleEventType } from './lifeCycle' @@ -22,11 +23,11 @@ describe('customerDataTelemetry', () => { let lifeCycle: LifeCycle const viewEvent = { type: RumEventType.VIEW } as RumEvent & Context - const baseConfiguration = { + const config: Partial = { telemetrySampleRate: 100, customerDataTelemetrySampleRate: 100, maxTelemetryEventsPerPage: 2, - } as RumConfiguration + } function generateBatch({ eventNumber, @@ -51,7 +52,8 @@ describe('customerDataTelemetry', () => { }) } - function setupCustomerTlemertyCollection(configuration: RumConfiguration = baseConfiguration) { + function setupCustomerTlemertyCollection(partialConfig: Partial = config) { + const configuration = mockRumConfiguration(partialConfig) batchFlushObservable = new Observable() lifeCycle = new LifeCycle() fakeContextBytesCount = 1 @@ -145,7 +147,7 @@ describe('customerDataTelemetry', () => { setupCustomerTlemertyCollection({ telemetrySampleRate: 100, customerDataTelemetrySampleRate: 0, - } as RumConfiguration) + }) generateBatch({ eventNumber: 1 }) clock.tick(MEASURES_PERIOD_DURATION) diff --git a/packages/rum-core/src/domain/view/setupViewTest.specHelper.ts b/packages/rum-core/src/domain/view/setupViewTest.specHelper.ts index 55f075ac1b..eb1629430d 100644 --- a/packages/rum-core/src/domain/view/setupViewTest.specHelper.ts +++ b/packages/rum-core/src/domain/view/setupViewTest.specHelper.ts @@ -1,14 +1,22 @@ -import type { BuildContext } from '../../../test' +import { Observable } from '@datadog/browser-core' +import { mockRumConfiguration, setupLocationObserver } from '../../../test' +import type { LifeCycle } from '../lifeCycle' import { LifeCycleEventType } from '../lifeCycle' import type { ViewEvent, ViewOptions } from './trackViews' import { trackViews } from './trackViews' export type ViewTest = ReturnType -export function setupViewTest( - { lifeCycle, location, domMutationObservable, configuration, locationChangeObservable }: BuildContext, - initialViewOptions?: ViewOptions -) { +interface ViewTrackingContext { + lifeCycle: LifeCycle + initialLocation?: string +} + +export function setupViewTest({ lifeCycle, initialLocation }: ViewTrackingContext, initialViewOptions?: ViewOptions) { + const domMutationObservable = new Observable() + const configuration = mockRumConfiguration() + const { locationChangeObservable, changeLocation } = setupLocationObserver(initialLocation) + const { handler: viewUpdateHandler, getViewEvent: getViewUpdate, @@ -38,6 +46,7 @@ export function setupViewTest( return { stop, startView, + changeLocation, updateViewName, addTiming, getViewUpdate, diff --git a/packages/rum-core/src/domain/view/trackViews.spec.ts b/packages/rum-core/src/domain/view/trackViews.spec.ts index a5ee4eb0a7..66c0c28bb4 100644 --- a/packages/rum-core/src/domain/view/trackViews.spec.ts +++ b/packages/rum-core/src/domain/view/trackViews.spec.ts @@ -6,14 +6,15 @@ import { relativeToClocks, relativeNow, ExperimentalFeature, + resetExperimentalFeatures, } from '@datadog/browser-core' -import { mockExperimentalFeatures } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../../test' -import { createPerformanceEntry, mockPerformanceObserver, setup } from '../../../test' +import type { Clock } from '@datadog/browser-core/test' +import { mockClock, mockExperimentalFeatures, registerCleanupTask } from '@datadog/browser-core/test' +import { createPerformanceEntry, mockPerformanceObserver } from '../../../test' import { RumEventType, ViewLoadingType } from '../../rawRumEvent.types' import type { RumEvent } from '../../rumEvent.types' -import { LifeCycleEventType } from '../lifeCycle' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' import type { RumPerformanceEntry } from '../../browser/performanceObservable' import { RumPerformanceEntryType } from '../../browser/performanceObservable' import type { ViewEvent } from './trackViews' @@ -23,21 +24,21 @@ import { setupViewTest } from './setupViewTest.specHelper' import { isLayoutShiftSupported } from './viewMetrics/trackCumulativeLayoutShift' describe('track views automatically', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let changeLocation: (to: string) => void let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext, { name: 'initial view name' }) - return viewTest - }) + viewTest = setupViewTest({ lifeCycle, initialLocation: '/foo' }, { name: 'initial view name' }) + changeLocation = viewTest.changeLocation + + registerCleanupTask(() => { + viewTest.stop() + }) }) describe('initial view', () => { it('should be created on start', () => { - setupBuilder.build() const { getViewCreate, getViewCreateCount } = viewTest expect(getViewCreateCount()).toBe(1) @@ -47,7 +48,6 @@ describe('track views automatically', () => { describe('location changes', () => { it('should create new view on path change', () => { - const { changeLocation } = setupBuilder.build() const { getViewCreateCount } = viewTest expect(getViewCreateCount()).toBe(1) @@ -58,7 +58,6 @@ describe('track views automatically', () => { }) it('should create new view on hash change from history', () => { - const { changeLocation } = setupBuilder.build() const { getViewCreateCount } = viewTest expect(getViewCreateCount()).toBe(1) @@ -74,7 +73,6 @@ describe('track views automatically', () => { } it('should not create a new view when it is an Anchor navigation', () => { - const { changeLocation } = setupBuilder.build() const { getViewCreateCount } = viewTest mockGetElementById() expect(getViewCreateCount()).toBe(1) @@ -85,7 +83,6 @@ describe('track views automatically', () => { }) it('should not create a new view when the search part of the hash changes', () => { - const { changeLocation } = setupBuilder.build() const { getViewCreateCount } = viewTest changeLocation('/foo#bar') expect(getViewCreateCount()).toBe(2) @@ -101,28 +98,36 @@ describe('track views automatically', () => { }) describe('view lifecycle', () => { - let setupBuilder: TestSetupBuilder + let lifeCycle: LifeCycle let viewTest: ViewTest + let clock: Clock let notifySpy: jasmine.Spy + let changeLocation: (to: string) => void beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - notifySpy = spyOn(buildContext.lifeCycle, 'notify').and.callThrough() + clock = mockClock() + lifeCycle = new LifeCycle() + notifySpy = spyOn(lifeCycle, 'notify').and.callThrough() + + viewTest = setupViewTest( + { lifeCycle, initialLocation: '/foo' }, + { + name: 'initial view name', + service: 'initial service', + version: 'initial version', + } + ) - viewTest = setupViewTest(buildContext, { - name: 'initial view name', - service: 'initial service', - version: 'initial version', - }) - return viewTest - }) + changeLocation = viewTest.changeLocation + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() + }) }) describe('expire session', () => { it('should end the view when the session expires', () => { - const { lifeCycle } = setupBuilder.build() const { getViewEndCount } = viewTest expect(getViewEndCount()).toBe(0) @@ -133,7 +138,6 @@ describe('view lifecycle', () => { }) it('should send a final view update', () => { - const { lifeCycle } = setupBuilder.build() const { getViewUpdateCount, getViewUpdate } = viewTest expect(getViewUpdateCount()).toBe(1) @@ -146,7 +150,6 @@ describe('view lifecycle', () => { }) it('should not start a new view if the session expired', () => { - const { lifeCycle } = setupBuilder.build() const { getViewCreateCount } = viewTest expect(getViewCreateCount()).toBe(1) @@ -157,7 +160,6 @@ describe('view lifecycle', () => { }) it('should not end the view if the view already ended', () => { - const { lifeCycle } = setupBuilder.build() const { getViewEndCount, getViewUpdateCount } = viewTest lifeCycle.notify(LifeCycleEventType.PAGE_EXITED, { reason: PageExitReason.UNLOADING }) @@ -174,7 +176,6 @@ describe('view lifecycle', () => { describe('renew session', () => { it('should create new view on renew session', () => { - const { lifeCycle } = setupBuilder.build() const { getViewCreateCount } = viewTest expect(getViewCreateCount()).toBe(1) @@ -186,7 +187,6 @@ describe('view lifecycle', () => { }) it('should use the current view name, service and version for the new view', () => { - const { lifeCycle, changeLocation } = setupBuilder.build() const { getViewCreateCount, getViewCreate, startView } = viewTest lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -264,8 +264,6 @@ describe('view lifecycle', () => { describe('session keep alive', () => { it('should emit a view update periodically', () => { - const { clock } = setupBuilder.withFakeClock().build() - const { getViewUpdateCount } = viewTest expect(getViewUpdateCount()).toEqual(1) @@ -276,7 +274,6 @@ describe('view lifecycle', () => { }) it('should not send periodical updates after the session has expired', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount } = viewTest lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -298,7 +295,6 @@ describe('view lifecycle', () => { it(`should ${ expectViewEnd ? '' : 'not ' }end the current view when the page is exiting for reason ${exitReason}`, () => { - const { lifeCycle } = setupBuilder.build() const { getViewEndCount } = viewTest expect(getViewEndCount()).toEqual(0) @@ -310,7 +306,6 @@ describe('view lifecycle', () => { }) it('should not create a new view when ending the view on page exit', () => { - const { lifeCycle } = setupBuilder.build() const { getViewCreateCount } = viewTest expect(getViewCreateCount()).toEqual(1) @@ -322,14 +317,11 @@ describe('view lifecycle', () => { }) it('should notify BEFORE_VIEW_CREATED before VIEW_CREATED', () => { - setupBuilder.build() - expect(notifySpy.calls.argsFor(0)[0]).toEqual(LifeCycleEventType.BEFORE_VIEW_CREATED) expect(notifySpy.calls.argsFor(1)[0]).toEqual(LifeCycleEventType.VIEW_CREATED) }) it('should notify AFTER_VIEW_ENDED after VIEW_ENDED', () => { - setupBuilder.build() const callsCount = notifySpy.calls.count() viewTest.stop() @@ -340,27 +332,28 @@ describe('view lifecycle', () => { }) describe('view loading type', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup() - .withFakeClock() - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + clock = mockClock() + + viewTest = setupViewTest({ lifeCycle }) + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() + }) }) it('should collect initial view type as "initial_load"', () => { - setupBuilder.build() const { getViewUpdate } = viewTest expect(getViewUpdate(0).loadingType).toEqual(ViewLoadingType.INITIAL_LOAD) }) it('should collect view type as "route_change" after a view change', () => { - setupBuilder.build() const { getViewUpdate, startView } = viewTest startView() @@ -371,18 +364,20 @@ describe('view loading type', () => { }) describe('view metrics', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let viewTest: ViewTest let notifyPerformanceEntries: (entries: RumPerformanceEntry[]) => void beforeEach(() => { + clock = mockClock() ;({ notifyPerformanceEntries } = mockPerformanceObserver()) - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + viewTest = setupViewTest({ lifeCycle }) + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() + }) }) describe('common view metrics', () => { @@ -390,7 +385,6 @@ describe('view metrics', () => { if (!isLayoutShiftSupported()) { pending('CLS web vital not supported') } - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount, getViewUpdate } = viewTest expect(getViewUpdateCount()).toEqual(1) @@ -416,7 +410,6 @@ describe('view metrics', () => { if (!isLayoutShiftSupported()) { pending('CLS web vital not supported') } - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest startView() expect(getViewCreateCount()).toEqual(2) @@ -435,7 +428,6 @@ describe('view metrics', () => { describe('initial view metrics', () => { it('should be updated when notified with a PERFORMANCE_ENTRY_COLLECTED event (throttled)', () => { - const { clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount, getViewUpdate } = viewTest expect(getViewUpdateCount()).toEqual(1) expect(getViewUpdate(0).initialViewMetrics).toEqual({}) @@ -459,7 +451,6 @@ describe('view metrics', () => { }) it('should be updated for 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest startView() expect(getViewCreateCount()).toEqual(2) @@ -481,7 +472,6 @@ describe('view metrics', () => { }) it('should not be updated 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest startView() expect(getViewCreateCount()).toEqual(2) @@ -505,7 +495,6 @@ describe('view metrics', () => { const VIEW_DURATION = 100 as Duration beforeEach(() => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount, getViewUpdate, startView } = viewTest expect(getViewUpdateCount()).toEqual(1) @@ -572,25 +561,24 @@ describe('view metrics', () => { }) describe('view is active', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup().beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest + viewTest = setupViewTest({ lifeCycle }) + + registerCleanupTask(() => { + viewTest.stop() }) }) it('should set initial view as active', () => { - setupBuilder.build() const { getViewUpdate } = viewTest expect(getViewUpdate(0).isActive).toBe(true) }) it('should set old view as inactive and new one as active after a route change', () => { - setupBuilder.build() const { getViewUpdate, startView } = viewTest startView() @@ -601,20 +589,21 @@ describe('view is active', () => { }) describe('view custom timings', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup() - .withFakeClock() - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + clock = mockClock() + viewTest = setupViewTest({ lifeCycle, initialLocation: '/foo' }) + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() + }) }) it('should add custom timing to current view', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, startView, addTiming } = viewTest startView() @@ -630,7 +619,6 @@ describe('view custom timings', () => { }) it('should add multiple custom timings', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, addTiming } = viewTest clock.tick(20) @@ -649,7 +637,6 @@ describe('view custom timings', () => { }) it('should update custom timing', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, addTiming } = viewTest clock.tick(20) @@ -679,7 +666,6 @@ describe('view custom timings', () => { }) it('should add custom timing with a specific timestamp', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, addTiming } = viewTest clock.tick(1234) @@ -693,7 +679,6 @@ describe('view custom timings', () => { }) it('should add custom timing with a specific relative time', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, addTiming } = viewTest clock.tick(1234) @@ -707,7 +692,6 @@ describe('view custom timings', () => { }) it('should sanitized timing name', () => { - const { clock } = setupBuilder.build() const { getViewUpdate, addTiming } = viewTest const displaySpy = spyOn(display, 'warn') @@ -724,7 +708,6 @@ describe('view custom timings', () => { }) it('should not add custom timing when the session has expired', () => { - const { clock, lifeCycle } = setupBuilder.build() const { getViewUpdateCount, addTiming } = viewTest lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -740,18 +723,21 @@ describe('view custom timings', () => { }) describe('start view', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup().beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest + clock = mockClock() + viewTest = setupViewTest({ lifeCycle }) + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() }) }) it('should start a new view', () => { - const { clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount, getViewUpdate, startView } = viewTest expect(getViewUpdateCount()).toBe(1) @@ -773,7 +759,6 @@ describe('start view', () => { }) it('should name the view', () => { - setupBuilder.build() const { getViewUpdate, startView } = viewTest startView() @@ -786,7 +771,6 @@ describe('start view', () => { }) it('should have service and version', () => { - setupBuilder.build() const { getViewUpdate, startView } = viewTest startView() @@ -814,7 +798,6 @@ describe('start view', () => { }) it('should ignore null service/version', () => { - setupBuilder.build() const { getViewUpdate, startView } = viewTest startView({ service: null, version: null }) @@ -827,7 +810,6 @@ describe('start view', () => { }) it('should use the provided clock to stop the current view and start the new one', () => { - const { clock } = setupBuilder.withFakeClock().build() const { getViewUpdate, startView } = viewTest clock.tick(100) @@ -839,20 +821,22 @@ describe('start view', () => { }) describe('view event count', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let viewTest: ViewTest beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + clock = mockClock() + + registerCleanupTask(() => { + viewTest.stop() + clock.cleanup() + resetExperimentalFeatures() + }) }) it('should be updated when notified with a RUM_EVENT_COLLECTED event', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, getViewUpdateCount } = viewTest lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, createFakeActionEvent()) @@ -863,7 +847,7 @@ describe('view event count', () => { }) it('should take child events occurring on view end into account', () => { - const { lifeCycle } = setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, getViewUpdateCount } = viewTest lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, () => { @@ -876,7 +860,7 @@ describe('view event count', () => { }) it('should be updated for 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest startView() expect(getViewCreateCount()).toEqual(2) @@ -897,7 +881,7 @@ describe('view event count', () => { }) it('should not be updated 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest startView() expect(getViewCreateCount()).toEqual(2) @@ -925,21 +909,9 @@ describe('view event count', () => { } describe('view specific context', () => { - let setupBuilder: TestSetupBuilder - let viewTest: ViewTest - - beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) - }) - it('should update view context if startView has context parameter', () => { mockExperimentalFeatures([ExperimentalFeature.VIEW_SPECIFIC_CONTEXT]) - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, startView } = viewTest startView({ context: { foo: 'bar' } }) @@ -948,7 +920,7 @@ describe('view event count', () => { it('should replace current context set on view event', () => { mockExperimentalFeatures([ExperimentalFeature.VIEW_SPECIFIC_CONTEXT]) - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, startView } = viewTest startView({ context: { foo: 'bar' } }) @@ -959,7 +931,7 @@ describe('view event count', () => { }) it('should not update view context if the feature is not enabled', () => { - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) const { getViewUpdate, startView } = viewTest startView({ context: { foo: 'bar' } }) @@ -968,19 +940,10 @@ describe('view event count', () => { }) describe('update view name', () => { - let setupBuilder: TestSetupBuilder - let viewTest: ViewTest - - beforeEach(() => { - setupBuilder = setup().beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) - }) - it('should update an undefined view name if the experimental feature is enabled', () => { mockExperimentalFeatures([ExperimentalFeature.UPDATE_VIEW_NAME]) - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) + const { getViewUpdate, startView, updateViewName } = viewTest startView() @@ -990,7 +953,8 @@ describe('view event count', () => { it('should update a defined view name if the experimental feature is enabled', () => { mockExperimentalFeatures([ExperimentalFeature.UPDATE_VIEW_NAME]) - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) + const { getViewUpdate, startView, updateViewName } = viewTest startView({ name: 'initial view name' }) @@ -999,7 +963,8 @@ describe('view event count', () => { }) it('should not update a defined view name if the experimental feature is not enabled', () => { - setupBuilder.build() + viewTest = setupViewTest({ lifeCycle }) + const { getViewUpdate, startView, updateViewName } = viewTest startView({ name: 'initial view name' }) diff --git a/packages/rum-core/src/domain/view/viewCollection.spec.ts b/packages/rum-core/src/domain/view/viewCollection.spec.ts index bc0740d8c8..a536155d10 100644 --- a/packages/rum-core/src/domain/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/view/viewCollection.spec.ts @@ -1,14 +1,20 @@ -import { noop, Observable } from '@datadog/browser-core' +import { Observable } from '@datadog/browser-core' import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' -import { registerCleanupTask, SPEC_ENDPOINTS } from '@datadog/browser-core/test' +import { registerCleanupTask } from '@datadog/browser-core/test' import type { RecorderApi } from '../../boot/rumPublicApi' -import { collectAndValidateRawRumEvents, mockPageStateHistory, noopRecorderApi } from '../../../test' +import { + collectAndValidateRawRumEvents, + mockFeatureFlagContexts, + mockPageStateHistory, + mockRumConfiguration, + noopRecorderApi, +} from '../../../test' import type { RawRumEvent, RawRumViewEvent } from '../../rawRumEvent.types' import { RumEventType, ViewLoadingType } from '../../rawRumEvent.types' import type { RawRumEventCollectedData } from '../lifeCycle' import { LifeCycle, LifeCycleEventType } from '../lifeCycle' import { PageState } from '../contexts/pageStateHistory' -import { validateAndBuildRumConfiguration, type RumConfiguration } from '../configuration' +import type { RumConfiguration } from '../configuration' import type { FeatureFlagContexts } from '../contexts/featureFlagContext' import type { LocationChange } from '../../browser/locationChangeObservable' import type { ViewEvent } from './trackViews' @@ -63,46 +69,32 @@ const VIEW: ViewEvent = { sessionIsActive: true, } -const baseFeatureFlagContexts: FeatureFlagContexts = { - findFeatureFlagEvaluations: () => undefined, - addFeatureFlagEvaluation: noop, - stop: noop, -} -const baseConfiguration: RumConfiguration = { - ...validateAndBuildRumConfiguration({ - clientToken: 'xxx', - applicationId: 'FAKE_APP_ID', - trackResources: true, - trackLongTasks: true, - })!, - ...SPEC_ENDPOINTS, -} -const pageStateHistory = mockPageStateHistory({ - findAll: () => [ - { start: 0 as ServerDuration, state: PageState.ACTIVE }, - { start: 10 as ServerDuration, state: PageState.PASSIVE }, - ], -}) - describe('viewCollection', () => { const lifeCycle = new LifeCycle() - let configuration: RumConfiguration - let featureFlagContexts: FeatureFlagContexts let getReplayStatsSpy: jasmine.Spy let rawRumEvents: Array> = [] - function setupViewCollection() { + function setupViewCollection( + partialConfiguration: Partial = {}, + partialFeatureFlagContexts: Partial = {} + ) { + getReplayStatsSpy = jasmine.createSpy() const domMutationObservable = new Observable() const locationChangeObservable = new Observable() const collectionResult = startViewCollection( lifeCycle, - configuration, + mockRumConfiguration(partialConfiguration), location, domMutationObservable, locationChangeObservable, - featureFlagContexts, - pageStateHistory, + mockFeatureFlagContexts(partialFeatureFlagContexts), + mockPageStateHistory({ + findAll: () => [ + { start: 0 as ServerDuration, state: PageState.ACTIVE }, + { start: 10 as ServerDuration, state: PageState.PASSIVE }, + ], + }), { ...noopRecorderApi, getReplayStats: getReplayStatsSpy, @@ -116,12 +108,6 @@ describe('viewCollection', () => { }) } - beforeEach(() => { - getReplayStatsSpy = jasmine.createSpy() - configuration = baseConfiguration - featureFlagContexts = baseFeatureFlagContexts - }) - it('should create view from view update', () => { setupViewCollection() lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) @@ -226,8 +212,7 @@ describe('viewCollection', () => { }) it('should include feature flags', () => { - featureFlagContexts = { ...baseFeatureFlagContexts, findFeatureFlagEvaluations: () => ({ feature: 'foo' }) } - setupViewCollection() + setupViewCollection({}, { findFeatureFlagEvaluations: () => ({ feature: 'foo' }) }) const view: ViewEvent = { ...VIEW, commonViewMetrics: { loadingTime: -20 as Duration } } lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, view) @@ -256,8 +241,7 @@ describe('viewCollection', () => { describe('with configuration.start_session_replay_recording_manually set', () => { it('should include startSessionReplayRecordingManually false', () => { // when configured to false - configuration = { ...baseConfiguration, startSessionReplayRecordingManually: false } - setupViewCollection() + setupViewCollection({ startSessionReplayRecordingManually: false }, {}) lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) expect( @@ -268,8 +252,7 @@ describe('viewCollection', () => { it('should include startSessionReplayRecordingManually true', () => { // when configured to true - configuration = { ...baseConfiguration, startSessionReplayRecordingManually: true } - setupViewCollection() + setupViewCollection({ startSessionReplayRecordingManually: true }, {}) lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, VIEW) expect( diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts index 16aa9265cd..aa6b6925fd 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts @@ -1,10 +1,9 @@ import type { RelativeTime } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' import { resetExperimentalFeatures, elapsed, ONE_SECOND } from '@datadog/browser-core' -import { appendElement, appendText, createPerformanceEntry } from '../../../../test' +import { appendElement, appendText, createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import type { RumConfiguration } from '../../configuration' import type { CumulativeLayoutShift } from './trackCumulativeLayoutShift' import { isLayoutShiftSupported, MAX_WINDOW_DURATION, trackCumulativeLayoutShift } from './trackCumulativeLayoutShift' @@ -30,7 +29,7 @@ describe('trackCumulativeLayoutShift', () => { get: () => (isLayoutShiftSupported ? ['layout-shift'] : []), }) - const clsTrackingesult = trackCumulativeLayoutShift({} as RumConfiguration, lifeCycle, viewStart, clsCallback) + const clsTrackingesult = trackCumulativeLayoutShift(mockRumConfiguration(), lifeCycle, viewStart, clsCallback) registerCleanupTask(() => { clsTrackingesult.stop() diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts index 1999a785bc..07ffb6fff5 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts @@ -1,9 +1,8 @@ import type { RelativeTime } from '@datadog/browser-core' import { registerCleanupTask, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import { createPerformanceEntry } from '../../../../test' +import { createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import { FCP_MAXIMUM_DELAY, trackFirstContentfulPaint } from './trackFirstContentfulPaint' import { trackFirstHidden } from './trackFirstHidden' @@ -13,7 +12,7 @@ describe('trackFirstContentfulPaint', () => { function startTrackingFCP() { fcpCallback = jasmine.createSpy() - const firstHidden = trackFirstHidden({} as RumConfiguration) + const firstHidden = trackFirstHidden(mockRumConfiguration()) const firstContentfulPaint = trackFirstContentfulPaint(lifeCycle, firstHidden, fcpCallback) registerCleanupTask(() => { diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackFirstHidden.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackFirstHidden.spec.ts index b9aaabbf58..5765acc61d 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackFirstHidden.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackFirstHidden.spec.ts @@ -1,17 +1,13 @@ import type { RelativeTime } from '@datadog/browser-core' import { DOM_EVENT } from '@datadog/browser-core' import { createNewEvent, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../../configuration' +import { mockRumConfiguration } from '../../../../test' import { trackFirstHidden } from './trackFirstHidden' describe('trackFirstHidden', () => { - let configuration: RumConfiguration + const configuration = mockRumConfiguration() let firstHidden: { timeStamp: RelativeTime; stop: () => void } - beforeEach(() => { - configuration = {} as RumConfiguration - }) - afterEach(() => { restorePageVisibility() firstHidden.stop() @@ -38,12 +34,6 @@ describe('trackFirstHidden', () => { }) describe('the page is initially visible', () => { - let configuration: RumConfiguration - - beforeEach(() => { - configuration = {} as RumConfiguration - }) - it('should return Infinity if the page was not hidden yet', () => { firstHidden = trackFirstHidden(configuration) expect(firstHidden.timeStamp).toBe(Infinity as RelativeTime) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts index 5a0571447d..1ad193fab1 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts @@ -1,8 +1,7 @@ import { type Duration, type RelativeTime } from '@datadog/browser-core' import { registerCleanupTask, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' -import { appendElement, appendText, createPerformanceEntry } from '../../../../test' +import { appendElement, appendText, createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' import type { FirstInput } from './trackFirstInput' import { trackFirstInput } from './trackFirstInput' @@ -11,10 +10,9 @@ import { trackFirstHidden } from './trackFirstHidden' describe('firstInputTimings', () => { const lifeCycle = new LifeCycle() let fitCallback: jasmine.Spy<(firstInput: FirstInput) => void> - let configuration: RumConfiguration function startFirstInputTracking() { - configuration = {} as RumConfiguration + const configuration = mockRumConfiguration() fitCallback = jasmine.createSpy() const firstHidden = trackFirstHidden(configuration) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts index 2029f0fb45..6775ff707c 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts @@ -2,9 +2,8 @@ import type { Duration, RelativeTime } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' import type { RumPerformanceEntry } from '../../../browser/performanceObservable' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import { createPerformanceEntry, mockPerformanceObserver } from '../../../../test' +import { createPerformanceEntry, mockPerformanceObserver, mockRumConfiguration } from '../../../../test' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import { trackInitialViewMetrics } from './trackInitialViewMetrics' describe('trackInitialViewMetrics', () => { @@ -16,7 +15,7 @@ describe('trackInitialViewMetrics', () => { beforeEach(() => { lifeCycle = new LifeCycle() - const configuration = {} as RumConfiguration + const configuration = mockRumConfiguration() scheduleViewUpdateSpy = jasmine.createSpy() setLoadEventSpy = jasmine.createSpy() ;({ notifyPerformanceEntries } = mockPerformanceObserver()) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts index 73e4c619f7..0dc2212d96 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts @@ -1,7 +1,7 @@ import type { Duration, RelativeTime } from '@datadog/browser-core' import { elapsed, resetExperimentalFeatures } from '@datadog/browser-core' import { registerCleanupTask } from '@datadog/browser-core/test' -import { appendElement, appendText, createPerformanceEntry } from '../../../../test' +import { appendElement, appendText, createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' import type { BrowserWindow, @@ -10,7 +10,6 @@ import type { } from '../../../browser/performanceObservable' import { ViewLoadingType } from '../../../rawRumEvent.types' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import { trackInteractionToNextPaint, trackViewInteractionCount, @@ -36,7 +35,7 @@ describe('trackInteractionToNextPaint', () => { interactionCountMock = mockInteractionCount() const interactionToNextPaintTracking = trackInteractionToNextPaint( - {} as RumConfiguration, + mockRumConfiguration(), viewStart, ViewLoadingType.INITIAL_LOAD, lifeCycle diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts index 8d48db6e5f..eba6d15886 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts @@ -7,9 +7,8 @@ import { registerCleanupTask, } from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import { appendElement, createPerformanceEntry } from '../../../../test' +import { appendElement, createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import type { LargestContentfulPaint } from './trackLargestContentfulPaint' import { LCP_MAXIMUM_DELAY, trackLargestContentfulPaint } from './trackLargestContentfulPaint' import { trackFirstHidden } from './trackFirstHidden' @@ -20,10 +19,10 @@ describe('trackLargestContentfulPaint', () => { let eventTarget: Window function startLCPTracking() { - const firstHidden = trackFirstHidden({} as RumConfiguration) + const firstHidden = trackFirstHidden(mockRumConfiguration()) const largestContentfulPaint = trackLargestContentfulPaint( lifeCycle, - {} as RumConfiguration, + mockRumConfiguration(), firstHidden, eventTarget, lcpCallback diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts index 0c71a38f50..03e1f3dec3 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts @@ -3,11 +3,10 @@ import { addDuration, clocksOrigin, Observable } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { mockClock, setPageVisibility, restorePageVisibility } from '@datadog/browser-core/test' import { ViewLoadingType } from '../../../rawRumEvent.types' -import { createPerformanceEntry } from '../../../../test' +import { createPerformanceEntry, mockRumConfiguration } from '../../../../test' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitPageActivityEnd' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' import { LifeCycle } from '../../lifeCycle' -import type { RumConfiguration } from '../../configuration' import { trackLoadingTime } from './trackLoadingTime' const BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY = (PAGE_ACTIVITY_VALIDATION_DELAY * 0.8) as Duration @@ -30,7 +29,7 @@ describe('trackLoadingTime', () => { const loadingTimeTracking = trackLoadingTime( lifeCycle, domMutationObservable, - {} as RumConfiguration, + mockRumConfiguration(), loadType, clocksOrigin(), loadingTimeCallback diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts index 2e2caa4603..66f167afee 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts @@ -3,8 +3,12 @@ import type { Clock } from '@datadog/browser-core/test' import { mockClock, registerCleanupTask } from '@datadog/browser-core/test' import type { RumPerformanceEntry } from '../../../browser/performanceObservable' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import { createPerformanceEntry, mockPerformanceObserver, mockPerformanceTiming } from '../../../../test' -import type { RumConfiguration } from '../../configuration' +import { + createPerformanceEntry, + mockPerformanceObserver, + mockPerformanceTiming, + mockRumConfiguration, +} from '../../../../test' import type { NavigationTimings } from './trackNavigationTimings' import { trackNavigationTimings } from './trackNavigationTimings' @@ -31,7 +35,7 @@ describe('trackNavigationTimings', () => { it('should provide navigation timing', () => { ;({ notifyPerformanceEntries } = mockPerformanceObserver()) - ;({ stop } = trackNavigationTimings({} as RumConfiguration, navigationTimingsCallback)) + ;({ stop } = trackNavigationTimings(mockRumConfiguration(), navigationTimingsCallback)) notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.NAVIGATION)]) expect(navigationTimingsCallback).toHaveBeenCalledOnceWith({ @@ -45,7 +49,7 @@ describe('trackNavigationTimings', () => { it('should discard incomplete navigation timing', () => { ;({ notifyPerformanceEntries } = mockPerformanceObserver()) - ;({ stop } = trackNavigationTimings({} as RumConfiguration, navigationTimingsCallback)) + ;({ stop } = trackNavigationTimings(mockRumConfiguration(), navigationTimingsCallback)) notifyPerformanceEntries([ createPerformanceEntry(RumPerformanceEntryType.NAVIGATION, { loadEventEnd: 0 as RelativeTime }), ]) @@ -57,7 +61,7 @@ describe('trackNavigationTimings', () => { clock = mockClock(new Date(0)) mockPerformanceTiming() removePerformanceObserver() - ;({ stop } = trackNavigationTimings({} as RumConfiguration, navigationTimingsCallback)) + ;({ stop } = trackNavigationTimings(mockRumConfiguration(), navigationTimingsCallback)) clock.tick(0) expect(navigationTimingsCallback).toHaveBeenCalledOnceWith({ diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts index 06755cee7b..6bc1ddeffc 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts @@ -2,12 +2,12 @@ import type { Duration, RelativeTime, Subscription, TimeStamp } from '@datadog/b import { DOM_EVENT, Observable, isIE } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' import { createNewEvent, mockClock, registerCleanupTask } from '@datadog/browser-core/test' -import type { RumConfiguration } from '../../configuration' +import { mockRumConfiguration } from '../../../../test' import type { ScrollMetrics, ScrollValues } from './trackScrollMetrics' import { createScrollValuesObservable, trackScrollMetrics } from './trackScrollMetrics' describe('createScrollValuesObserver', () => { - const scrollObservable = createScrollValuesObservable({} as RumConfiguration, 0) + const scrollObservable = createScrollValuesObservable(mockRumConfiguration(), 0) let subscription: Subscription const newScroll = () => { @@ -57,7 +57,7 @@ describe('trackScrollMetrics', () => { scrollMetricsCallback = jasmine.createSpy() clock = mockClock() trackScrollMetrics( - {} as RumConfiguration, + mockRumConfiguration(), { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, scrollMetricsCallback, scrollObservable diff --git a/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts b/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts index cb1db9332b..055b78be38 100644 --- a/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts +++ b/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts @@ -1,8 +1,8 @@ import type { RelativeTime, Subscription } from '@datadog/browser-core' import { Observable, ONE_SECOND, getTimeStamp } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' -import { mockClock, SPEC_ENDPOINTS } from '@datadog/browser-core/test' -import { createPerformanceEntry, mockPerformanceObserver } from '../../test' +import { mockClock } from '@datadog/browser-core/test' +import { createPerformanceEntry, mockPerformanceObserver, mockRumConfiguration } from '../../test' import type { RumPerformanceEntry } from '../browser/performanceObservable' import { RumPerformanceEntryType } from '../browser/performanceObservable' import { LifeCycle, LifeCycleEventType } from './lifeCycle' @@ -15,7 +15,6 @@ import { createPageActivityObservable, } from './waitPageActivityEnd' import type { RumConfiguration } from './configuration' -import { validateAndBuildRumConfiguration } from './configuration' // Used to wait some time after the creation of an action const BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY = PAGE_ACTIVITY_VALIDATION_DELAY * 0.8 @@ -42,15 +41,7 @@ function eventsCollector() { } } -const RUM_CONFIGURATION: RumConfiguration = { - ...validateAndBuildRumConfiguration({ - clientToken: 'xxx', - applicationId: 'AppId', - trackResources: true, - trackLongTasks: true, - })!, - ...SPEC_ENDPOINTS, -} +const RUM_CONFIGURATION = mockRumConfiguration() describe('createPageActivityObservable', () => { const { events, pushEvent } = eventsCollector() diff --git a/packages/rum-core/test/index.ts b/packages/rum-core/test/index.ts index ec9cbb9a35..a71e31e960 100644 --- a/packages/rum-core/test/index.ts +++ b/packages/rum-core/test/index.ts @@ -5,6 +5,8 @@ export * from './formatValidation' export * from './mockCiVisibilityValues' export * from './mockRumSessionManager' export * from './noopRecorderApi' -export * from './testSetupBuilder' export * from './emulate/mockPerformanceObserver' export * from './mockPageStateHistory' +export * from './mockRumConfiguration' +export * from './locationChangeSetup' +export * from './mockContexts' diff --git a/packages/rum-core/test/locationChangeSetup.ts b/packages/rum-core/test/locationChangeSetup.ts new file mode 100644 index 0000000000..8e881ac980 --- /dev/null +++ b/packages/rum-core/test/locationChangeSetup.ts @@ -0,0 +1,19 @@ +import { buildLocation } from '@datadog/browser-core/test' +import { assign, Observable } from '@datadog/browser-core' +import type { LocationChange } from '../src/browser/locationChangeObservable' + +export function setupLocationObserver(initialLocation?: string) { + const fakeLocation = initialLocation ? buildLocation(initialLocation) : location + const locationChangeObservable = new Observable() + + function changeLocation(to: string) { + const currentLocation = { ...fakeLocation } + assign(fakeLocation, buildLocation(to, fakeLocation.href)) + locationChangeObservable.notify({ + oldLocation: currentLocation as Location, + newLocation: fakeLocation, + }) + } + + return { fakeLocation, locationChangeObservable, changeLocation } +} diff --git a/packages/rum-core/test/mockContexts.ts b/packages/rum-core/test/mockContexts.ts new file mode 100644 index 0000000000..8b0b687651 --- /dev/null +++ b/packages/rum-core/test/mockContexts.ts @@ -0,0 +1,45 @@ +import { noop } from '@datadog/browser-core' +import type { ActionContexts } from '../src/domain/action/trackClickActions' +import type { DisplayContext } from '../src/domain/contexts/displayContext' +import type { UrlContexts } from '../src/domain/contexts/urlContexts' +import type { ViewContexts } from '../src/domain/contexts/viewContexts' +import type { FeatureFlagContexts } from '../src/domain/contexts/featureFlagContext' + +export function mockUrlContexts(fakeLocation: Location = location): UrlContexts { + return { + findUrl: () => ({ + url: fakeLocation.href, + referrer: document.referrer, + }), + stop: noop, + } +} + +export function mockViewContexts(): ViewContexts { + return { + findView: () => undefined, + stop: noop, + } +} + +export function mockActionContexts(): ActionContexts { + return { + findActionId: () => '7890', + } +} + +export function mockDisplayContext(): DisplayContext { + return { + get: () => ({ viewport: { height: 0, width: 0 } }), + stop: noop, + } +} + +export function mockFeatureFlagContexts(partialContext: Partial = {}): FeatureFlagContexts { + return { + findFeatureFlagEvaluations: () => undefined, + addFeatureFlagEvaluation: noop, + stop: noop, + ...partialContext, + } +} diff --git a/packages/rum-core/test/mockRumConfiguration.ts b/packages/rum-core/test/mockRumConfiguration.ts new file mode 100644 index 0000000000..dadfdb4f70 --- /dev/null +++ b/packages/rum-core/test/mockRumConfiguration.ts @@ -0,0 +1,18 @@ +import type { RumConfiguration } from '@datadog/browser-rum-core' +import { SPEC_ENDPOINTS } from '@datadog/browser-core/test' +import { validateAndBuildRumConfiguration } from '../src/domain/configuration' + +export function mockRumConfiguration(partialConfig: Partial = {}): RumConfiguration { + const FAKE_APP_ID = 'appId' + const baseConfig: RumConfiguration = { + ...validateAndBuildRumConfiguration({ + clientToken: 'xxx', + applicationId: FAKE_APP_ID, + trackResources: true, + trackLongTasks: true, + })!, + ...SPEC_ENDPOINTS, + } + + return { ...baseConfig, ...partialConfig } +} diff --git a/packages/rum-core/test/testSetupBuilder.ts b/packages/rum-core/test/testSetupBuilder.ts deleted file mode 100644 index 063f3f47ee..0000000000 --- a/packages/rum-core/test/testSetupBuilder.ts +++ /dev/null @@ -1,220 +0,0 @@ -import type { ContextManager, CustomerDataTrackerManager } from '@datadog/browser-core' -import { - assign, - createContextManager, - createCustomerDataTrackerManager, - CustomerDataType, - noop, - Observable, -} from '@datadog/browser-core' -import type { Clock } from '@datadog/browser-core/test' -import { registerCleanupTask, mockClock, buildLocation, SPEC_ENDPOINTS } from '@datadog/browser-core/test' -import type { LocationChange } from '../src/browser/locationChangeObservable' -import type { RumConfiguration } from '../src/domain/configuration' -import { validateAndBuildRumConfiguration } from '../src/domain/configuration' -import type { FeatureFlagContexts } from '../src/domain/contexts/featureFlagContext' -import type { PageStateHistory } from '../src/domain/contexts/pageStateHistory' -import type { UrlContexts } from '../src/domain/contexts/urlContexts' -import type { ViewContexts } from '../src/domain/contexts/viewContexts' -import type { RawRumEventCollectedData } from '../src/domain/lifeCycle' -import { LifeCycle } from '../src/domain/lifeCycle' -import type { ActionContexts } from '../src/domain/action/actionCollection' -import type { RumSessionManager } from '../src/domain/rumSessionManager' -import type { DisplayContext } from '../src/domain/contexts/displayContext' -import { collectAndValidateRawRumEvents } from './formatValidation' -import { createRumSessionManagerMock } from './mockRumSessionManager' -import { mockPageStateHistory } from './mockPageStateHistory' - -export interface TestSetupBuilder { - withFakeLocation: (initialUrl: string) => TestSetupBuilder - withSessionManager: (sessionManager: RumSessionManager) => TestSetupBuilder - withConfiguration: (overrides: Partial) => TestSetupBuilder - withViewContexts: (stub: Partial) => TestSetupBuilder - withActionContexts: (stub: ActionContexts) => TestSetupBuilder - withPageStateHistory: (stub: Partial) => TestSetupBuilder - withFeatureFlagContexts: (stub: Partial) => TestSetupBuilder - withFakeClock: () => TestSetupBuilder - beforeBuild: (callback: BeforeBuildCallback) => TestSetupBuilder - - clock: Clock | undefined - domMutationObservable: Observable - build: () => TestIO -} - -type BeforeBuildCallback = (buildContext: BuildContext) => void | { stop: () => void } - -export interface BuildContext { - lifeCycle: LifeCycle - domMutationObservable: Observable - locationChangeObservable: Observable - configuration: Readonly - sessionManager: RumSessionManager - location: Location - applicationId: string - viewContexts: ViewContexts - actionContexts: ActionContexts - displayContext: DisplayContext - pageStateHistory: PageStateHistory - featureFlagContexts: FeatureFlagContexts - urlContexts: UrlContexts - globalContextManager: ContextManager - userContextManager: ContextManager - customerDataTrackerManager: CustomerDataTrackerManager -} - -export interface TestIO { - lifeCycle: LifeCycle - domMutationObservable: Observable - changeLocation: (to: string) => void - clock: Clock - fakeLocation: Partial - sessionManager: RumSessionManager - rawRumEvents: RawRumEventCollectedData[] -} - -export function setup(): TestSetupBuilder { - let sessionManager: RumSessionManager = createRumSessionManagerMock().setId('1234') - const lifeCycle = new LifeCycle() - const domMutationObservable = new Observable() - const locationChangeObservable = new Observable() - const cleanupTasks: Array<() => void> = [] - const beforeBuildTasks: BeforeBuildCallback[] = [] - - let clock: Clock - let fakeLocation: Partial = location - let viewContexts: ViewContexts - const urlContexts: UrlContexts = { - findUrl: () => ({ - url: fakeLocation.href!, - referrer: document.referrer, - }), - stop: noop, - } - let featureFlagContexts: FeatureFlagContexts = { - findFeatureFlagEvaluations: () => undefined, - addFeatureFlagEvaluation: noop, - stop: noop, - } - let actionContexts: ActionContexts = { - findActionId: noop as () => undefined, - } - const displayContext: DisplayContext = { - get: () => ({ viewport: { height: 0, width: 0 } }), - stop: noop, - } - - const customerDataTrackerManager = createCustomerDataTrackerManager() - const globalContextManager = createContextManager( - customerDataTrackerManager.getOrCreateTracker(CustomerDataType.GlobalContext) - ) - const userContextManager = createContextManager(customerDataTrackerManager.getOrCreateTracker(CustomerDataType.User)) - let pageStateHistory = mockPageStateHistory() - const FAKE_APP_ID = 'appId' - const configuration: RumConfiguration = { - ...validateAndBuildRumConfiguration({ - clientToken: 'xxx', - applicationId: FAKE_APP_ID, - trackResources: true, - trackLongTasks: true, - })!, - ...SPEC_ENDPOINTS, - } - - function changeLocation(to: string) { - const currentLocation = { ...fakeLocation } - assign(fakeLocation, buildLocation(to, fakeLocation.href)) - locationChangeObservable.notify({ - oldLocation: currentLocation as Location, - newLocation: fakeLocation as Location, - }) - } - - const rawRumEvents = collectAndValidateRawRumEvents(lifeCycle) - - const setupBuilder: TestSetupBuilder = { - domMutationObservable, - get clock() { - return clock - }, - - withFakeLocation(initialUrl: string) { - fakeLocation = buildLocation(initialUrl) - return setupBuilder - }, - withSessionManager(stub: RumSessionManager) { - sessionManager = stub - return setupBuilder - }, - withConfiguration(overrides: Partial) { - assign(configuration, overrides) - return setupBuilder - }, - withViewContexts(stub: Partial) { - viewContexts = stub as ViewContexts - return setupBuilder - }, - withActionContexts(stub: ActionContexts) { - actionContexts = stub - return setupBuilder - }, - withPageStateHistory(stub: Partial) { - pageStateHistory = { ...pageStateHistory, ...stub } - return setupBuilder - }, - withFeatureFlagContexts(stub: Partial) { - featureFlagContexts = { ...featureFlagContexts, ...stub } - return setupBuilder - }, - withFakeClock() { - clock = mockClock() - return setupBuilder - }, - - beforeBuild(callback: BeforeBuildCallback) { - beforeBuildTasks.push(callback) - return setupBuilder - }, - build() { - beforeBuildTasks.forEach((task) => { - const result = task({ - lifeCycle, - domMutationObservable, - locationChangeObservable, - viewContexts, - urlContexts, - actionContexts, - displayContext, - pageStateHistory, - featureFlagContexts, - sessionManager, - applicationId: FAKE_APP_ID, - configuration, - location: fakeLocation as Location, - globalContextManager, - userContextManager, - customerDataTrackerManager, - }) - if (result && result.stop) { - cleanupTasks.push(result.stop) - } - }) - return { - clock, - fakeLocation, - lifeCycle, - domMutationObservable, - rawRumEvents, - sessionManager, - changeLocation, - } - }, - } - registerCleanupTask(() => { - cleanupTasks.forEach((task) => task()) - // perform these steps at the end to generate correct events in cleanup and validate them - if (clock) { - clock.cleanup() - } - }) - return setupBuilder -} diff --git a/packages/rum/src/boot/recorderApi.spec.ts b/packages/rum/src/boot/recorderApi.spec.ts index 1cc63f0610..33c647f364 100644 --- a/packages/rum/src/boot/recorderApi.spec.ts +++ b/packages/rum/src/boot/recorderApi.spec.ts @@ -1,10 +1,10 @@ import type { DeflateEncoder, DeflateWorker, DeflateWorkerAction } from '@datadog/browser-core' import { BridgeCapability, PageExitReason, display, isIE } from '@datadog/browser-core' -import type { RecorderApi, ViewContexts, LifeCycle, RumConfiguration } from '@datadog/browser-rum-core' -import { LifeCycleEventType } from '@datadog/browser-rum-core' +import type { RecorderApi, RumSessionManager } from '@datadog/browser-rum-core' +import { LifeCycle, LifeCycleEventType } from '@datadog/browser-rum-core' import { mockEventBridge, createNewEvent, registerCleanupTask } from '@datadog/browser-core/test' -import type { RumSessionManagerMock, TestSetupBuilder } from '../../../rum-core/test' -import { createRumSessionManagerMock, setup } from '../../../rum-core/test' +import type { RumSessionManagerMock } from '../../../rum-core/test' +import { createRumSessionManagerMock, mockRumConfiguration, mockViewContexts } from '../../../rum-core/test' import type { CreateDeflateWorker } from '../domain/deflate' import { MockWorker } from '../../test' import { resetDeflateWorkerState } from '../domain/deflate' @@ -13,7 +13,7 @@ import type { StartRecording } from './recorderApi' import { makeRecorderApi } from './recorderApi' describe('makeRecorderApi', () => { - let setupBuilder: TestSetupBuilder + let lifeCycle: LifeCycle let recorderApi: RecorderApi let startRecordingSpy: jasmine.Spy let stopRecordingSpy: jasmine.Spy<() => void> @@ -21,53 +21,53 @@ describe('makeRecorderApi', () => { let createDeflateWorkerSpy: jasmine.Spy let rumInit: (options?: { worker?: DeflateWorker }) => void - let startSessionReplayRecordingManually: boolean - beforeEach(() => { + function setupRecorderApi({ + sessionManager, + startSessionReplayRecordingManually, + }: { sessionManager?: RumSessionManager; startSessionReplayRecordingManually?: boolean } = {}) { if (isIE()) { pending('IE not supported') } - startSessionReplayRecordingManually = false mockWorker = new MockWorker() createDeflateWorkerSpy = jasmine.createSpy('createDeflateWorkerSpy').and.callFake(() => mockWorker) spyOn(display, 'error') - setupBuilder = setup().beforeBuild(({ lifeCycle, sessionManager }) => { - stopRecordingSpy = jasmine.createSpy('stopRecording') - startRecordingSpy = jasmine.createSpy('startRecording').and.callFake(() => ({ - stop: stopRecordingSpy, - })) - - recorderApi = makeRecorderApi(startRecordingSpy, createDeflateWorkerSpy) - rumInit = ({ worker } = {}) => { - recorderApi.onRumStart( - lifeCycle, - { startSessionReplayRecordingManually } as RumConfiguration, - sessionManager, - {} as ViewContexts, - worker - ) - } - }) + lifeCycle = new LifeCycle() + stopRecordingSpy = jasmine.createSpy('stopRecording') + startRecordingSpy = jasmine.createSpy('startRecording').and.callFake(() => ({ + stop: stopRecordingSpy, + })) + + recorderApi = makeRecorderApi(startRecordingSpy, createDeflateWorkerSpy) + rumInit = ({ worker } = {}) => { + recorderApi.onRumStart( + lifeCycle, + mockRumConfiguration({ startSessionReplayRecordingManually: startSessionReplayRecordingManually ?? false }), + sessionManager ?? createRumSessionManagerMock().setId('1234'), + mockViewContexts(), + worker + ) + } registerCleanupTask(() => { resetDeflateWorkerState() replayStats.resetReplayStats() }) - }) + } describe('recorder boot', () => { describe('with automatic start', () => { it('starts recording when init() is called', () => { - setupBuilder.build() + setupRecorderApi() expect(startRecordingSpy).not.toHaveBeenCalled() rumInit() expect(startRecordingSpy).toHaveBeenCalled() }) it('starts recording after the DOM is loaded', () => { - setupBuilder.build() + setupRecorderApi() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -78,16 +78,14 @@ describe('makeRecorderApi', () => { describe('with manual start', () => { it('does not start recording when init() is called', () => { - startSessionReplayRecordingManually = true - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) expect(startRecordingSpy).not.toHaveBeenCalled() rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() }) it('does not start recording after the DOM is loaded', () => { - startSessionReplayRecordingManually = true - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -98,12 +96,8 @@ describe('makeRecorderApi', () => { }) describe('recorder start', () => { - beforeEach(() => { - startSessionReplayRecordingManually = true - }) - it('ignores additional start calls while recording is already started', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit() recorderApi.start() recorderApi.start() @@ -112,7 +106,7 @@ describe('makeRecorderApi', () => { }) it('ignores restart before the DOM is loaded', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() recorderApi.stop() @@ -122,14 +116,20 @@ describe('makeRecorderApi', () => { }) it('ignores start calls if the session is not tracked', () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setNotTracked()).build() + setupRecorderApi({ + sessionManager: createRumSessionManagerMock().setNotTracked(), + startSessionReplayRecordingManually: true, + }) rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() }) it('ignores start calls if the session is tracked without session replay', () => { - setupBuilder.withSessionManager(createRumSessionManagerMock().setTrackedWithoutSessionReplay()).build() + setupRecorderApi({ + sessionManager: createRumSessionManagerMock().setTrackedWithoutSessionReplay(), + startSessionReplayRecordingManually: true, + }) rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -138,12 +138,13 @@ describe('makeRecorderApi', () => { it('should start recording if session is tracked without session replay when forced', () => { const setForcedReplaySpy = jasmine.createSpy() - setupBuilder - .withSessionManager({ + setupRecorderApi({ + sessionManager: { ...createRumSessionManagerMock().setTrackedWithoutSessionReplay(), setForcedReplay: setForcedReplaySpy, - }) - .build() + }, + startSessionReplayRecordingManually: true, + }) rumInit() recorderApi.start({ force: true }) @@ -152,7 +153,7 @@ describe('makeRecorderApi', () => { }) it('uses the previously created worker if available', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit({ worker: mockWorker }) recorderApi.start() expect(createDeflateWorkerSpy).not.toHaveBeenCalled() @@ -160,7 +161,7 @@ describe('makeRecorderApi', () => { }) it('does not start recording if worker creation fails', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit() createDeflateWorkerSpy.and.throwError('Crash') recorderApi.start() @@ -168,7 +169,7 @@ describe('makeRecorderApi', () => { }) it('stops recording if worker initialization fails', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit() recorderApi.start() @@ -178,7 +179,7 @@ describe('makeRecorderApi', () => { }) it('restarting the recording should not reset the worker action id', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit() recorderApi.start() @@ -202,7 +203,7 @@ describe('makeRecorderApi', () => { it('should start recording when the bridge supports records', () => { mockEventBridge({ capabilities: [BridgeCapability.RECORDS] }) - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() expect(startRecordingSpy).toHaveBeenCalled() @@ -211,7 +212,7 @@ describe('makeRecorderApi', () => { it('should not start recording when the bridge does not support records', () => { mockEventBridge({ capabilities: [] }) - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -231,7 +232,7 @@ describe('makeRecorderApi', () => { }) it('does not start recording', () => { - setupBuilder.build() + setupRecorderApi({ startSessionReplayRecordingManually: true }) recorderApi.start() rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -241,7 +242,7 @@ describe('makeRecorderApi', () => { describe('recorder stop', () => { it('ignores calls while recording is already stopped', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.stop() recorderApi.stop() @@ -250,7 +251,7 @@ describe('makeRecorderApi', () => { }) it('prevents recording to start when the DOM is loaded', () => { - setupBuilder.build() + setupRecorderApi() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() recorderApi.stop() @@ -261,11 +262,9 @@ describe('makeRecorderApi', () => { describe('recorder lifecycle', () => { let sessionManager: RumSessionManagerMock - let lifeCycle: LifeCycle beforeEach(() => { sessionManager = createRumSessionManagerMock() - setupBuilder.withSessionManager(sessionManager) - ;({ lifeCycle } = setupBuilder.build()) + setupRecorderApi({ sessionManager }) }) // prevent getting records after the before_unload event has been triggered. @@ -459,14 +458,14 @@ describe('makeRecorderApi', () => { describe('isRecording', () => { it('is false when recording has not been started', () => { - setupBuilder.build() + setupRecorderApi() rumInit() expect(recorderApi.isRecording()).toBeFalse() }) it('is false when the worker is not yet initialized', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() @@ -474,7 +473,7 @@ describe('makeRecorderApi', () => { }) it('is false when the worker failed to initialize', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() @@ -484,7 +483,7 @@ describe('makeRecorderApi', () => { }) it('is true when recording is started and the worker is initialized', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() @@ -494,7 +493,7 @@ describe('makeRecorderApi', () => { }) it('is false before the DOM is loaded', () => { - setupBuilder.build() + setupRecorderApi() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() @@ -514,14 +513,14 @@ describe('makeRecorderApi', () => { const VIEW_ID = 'xxx' it('is undefined when recording has not been started', () => { - setupBuilder.build() + setupRecorderApi() rumInit() expect(recorderApi.getReplayStats(VIEW_ID)).toBeUndefined() }) it('is undefined when the worker is not yet initialized', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() @@ -530,7 +529,7 @@ describe('makeRecorderApi', () => { }) it('is undefined when the worker failed to initialize', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() @@ -541,7 +540,7 @@ describe('makeRecorderApi', () => { }) it('is defined when recording is started and the worker is initialized', () => { - setupBuilder.build() + setupRecorderApi() rumInit() recorderApi.start() diff --git a/packages/rum/src/boot/startRecording.spec.ts b/packages/rum/src/boot/startRecording.spec.ts index 53f48487a0..bf4ade2811 100644 --- a/packages/rum/src/boot/startRecording.spec.ts +++ b/packages/rum/src/boot/startRecording.spec.ts @@ -1,12 +1,11 @@ import type { TimeStamp, HttpRequest } from '@datadog/browser-core' import { PageExitReason, DefaultPrivacyLevel, noop, isIE, DeflateEncoderStreamId } from '@datadog/browser-core' -import type { LifeCycle, ViewCreatedEvent, RumConfiguration } from '@datadog/browser-rum-core' -import { LifeCycleEventType, startViewContexts } from '@datadog/browser-rum-core' -import type { Clock } from '@datadog/browser-core/test' +import type { ViewCreatedEvent } from '@datadog/browser-rum-core' +import { LifeCycle, LifeCycleEventType, startViewContexts } from '@datadog/browser-rum-core' import { collectAsyncCalls, createNewEvent, mockEventBridge, registerCleanupTask } from '@datadog/browser-core/test' import type { ViewEndedEvent } from 'packages/rum-core/src/domain/view/trackViews' -import type { RumSessionManagerMock, TestSetupBuilder } from '../../../rum-core/test' -import { appendElement, createRumSessionManagerMock, setup } from '../../../rum-core/test' +import type { RumSessionManagerMock } from '../../../rum-core/test' +import { appendElement, createRumSessionManagerMock, mockRumConfiguration } from '../../../rum-core/test' import { recordsPerFullSnapshot, readReplayPayload } from '../../test' import { setSegmentBytesLimit } from '../domain/segmentCollection' @@ -19,70 +18,58 @@ import { startRecording } from './startRecording' const VIEW_TIMESTAMP = 1 as TimeStamp describe('startRecording', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let sessionManager: RumSessionManagerMock let viewId: string let textField: HTMLInputElement let requestSendSpy: jasmine.Spy let stopRecording: () => void - let clock: Clock | undefined - let configuration: RumConfiguration - beforeEach(() => { - if (isIE()) { - pending('IE not supported') - } - configuration = {} as RumConfiguration + function setupStartRecording() { + const configuration = mockRumConfiguration({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW }) resetReplayStats() - sessionManager = createRumSessionManagerMock() - viewId = 'view-id' - - textField = appendElement('') as HTMLInputElement - const worker = startDeflateWorker(configuration, 'Session Replay', noop) - setupBuilder = setup() - .withSessionManager(sessionManager) - .withConfiguration({ - defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, - }) - .beforeBuild(({ lifeCycle, configuration, sessionManager }) => { - requestSendSpy = jasmine.createSpy() - const httpRequest = { - send: requestSendSpy, - sendOnExit: requestSendSpy, - } - - const deflateEncoder = createDeflateEncoder(configuration, worker!, DeflateEncoderStreamId.REPLAY) - const viewContexts = startViewContexts(lifeCycle) - initialView(lifeCycle) - - const recording = startRecording( - lifeCycle, - configuration, - sessionManager, - viewContexts, - deflateEncoder, - httpRequest - ) - stopRecording = recording ? recording.stop : noop - return { - stop: () => { - stopRecording() - deflateEncoder.stop() - }, - } - }) + requestSendSpy = jasmine.createSpy() + const httpRequest = { + send: requestSendSpy, + sendOnExit: requestSendSpy, + } + + const deflateEncoder = createDeflateEncoder(configuration, worker!, DeflateEncoderStreamId.REPLAY) + const viewContexts = startViewContexts(lifeCycle) + initialView(lifeCycle) + + const recording = startRecording( + lifeCycle, + configuration, + sessionManager, + viewContexts, + deflateEncoder, + httpRequest + ) + stopRecording = recording ? recording.stop : noop registerCleanupTask(() => { + stopRecording() + deflateEncoder.stop() setSegmentBytesLimit() - clock?.cleanup() resetDeflateWorkerState() }) + } + + beforeEach(() => { + if (isIE()) { + pending('IE not supported') + } + sessionManager = createRumSessionManagerMock() + viewId = 'view-id' + + textField = appendElement('') as HTMLInputElement }) it('sends recorded segments with valid context', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() flushSegment(lifeCycle) const requests = await readSentRequests(1) @@ -110,7 +97,7 @@ describe('startRecording', () => { }) it('flushes the segment when its compressed data reaches the segment bytes limit', async () => { - setupBuilder.build() + setupStartRecording() const inputCount = 150 const inputEvent = createNewEvent('input', { target: textField }) for (let i = 0; i < inputCount; i += 1) { @@ -125,7 +112,7 @@ describe('startRecording', () => { }) it('stops sending new segment when the session is expired', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 })) @@ -141,7 +128,7 @@ describe('startRecording', () => { it('restarts sending segments when the session is renewed', async () => { sessionManager.setNotTracked() - const { lifeCycle } = setupBuilder.build() + setupStartRecording() document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 })) @@ -157,7 +144,7 @@ describe('startRecording', () => { }) it('flushes pending mutations before ending the view', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() appendElement('
') changeView(lifeCycle) @@ -173,7 +160,7 @@ describe('startRecording', () => { }) it('flushes pending mutations before ending the view, even after the segment has been flushed', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() // flush segment right before the view change to set the segment collection in the waiting state flushSegment(lifeCycle) @@ -188,7 +175,7 @@ describe('startRecording', () => { it('does not split Meta, Focus and FullSnapshot records between multiple segments when taking a full snapshot', async () => { setSegmentBytesLimit(0) - setupBuilder.build() + setupStartRecording() const requests = await readSentRequests(1) expect(requests[0].segment.records[0].type).toBe(RecordType.Meta) @@ -198,7 +185,7 @@ describe('startRecording', () => { describe('when calling stop()', () => { it('stops collecting records', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 })) stopRecording() @@ -210,7 +197,7 @@ describe('startRecording', () => { }) it('stops taking full snapshots on view creation', async () => { - const { lifeCycle } = setupBuilder.build() + setupStartRecording() stopRecording() changeView(lifeCycle) @@ -223,7 +210,7 @@ describe('startRecording', () => { it('should send records through the bridge when it is present', () => { const eventBridge = mockEventBridge() - const { lifeCycle } = setupBuilder.build() + setupStartRecording() const sendSpy = spyOn(eventBridge, 'send') // send click record