diff --git a/packages/rum-core/src/domain/contexts/featureFlagContext.spec.ts b/packages/rum-core/src/domain/contexts/featureFlagContext.spec.ts index d26f31f0b3..21588e1866 100644 --- a/packages/rum-core/src/domain/contexts/featureFlagContext.spec.ts +++ b/packages/rum-core/src/domain/contexts/featureFlagContext.spec.ts @@ -1,38 +1,35 @@ import type { CustomerDataTracker, RelativeTime } from '@datadog/browser-core' import { relativeToClocks, createCustomerDataTracker, noop } from '@datadog/browser-core' -import type { TestSetupBuilder } from '../../../test' -import { setup } from '../../../test' -import { LifeCycleEventType } from '../lifeCycle' +import type { Clock } from '@datadog/browser-core/test' +import { mockClock, registerCleanupTask } from '@datadog/browser-core/test' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' import type { ViewCreatedEvent, ViewEndedEvent } from '../view/trackViews' import type { FeatureFlagContexts } from './featureFlagContext' import { startFeatureFlagContexts } from './featureFlagContext' describe('featureFlagContexts', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let clock: Clock let customerDataTracker: CustomerDataTracker let featureFlagContexts: FeatureFlagContexts beforeEach(() => { - setupBuilder = setup().beforeBuild(({ lifeCycle }) => { - customerDataTracker = createCustomerDataTracker(noop) - featureFlagContexts = startFeatureFlagContexts(lifeCycle, customerDataTracker) - }) - }) + clock = mockClock() + customerDataTracker = createCustomerDataTracker(noop) + featureFlagContexts = startFeatureFlagContexts(lifeCycle, customerDataTracker) - afterEach(() => { - featureFlagContexts.stop() + registerCleanupTask(() => { + clock.cleanup() + featureFlagContexts.stop() + }) }) it('should return undefined before the initial view', () => { - setupBuilder.build() - expect(featureFlagContexts.findFeatureFlagEvaluations()).toBeUndefined() }) describe('addFeatureFlagEvaluation', () => { it('should add feature flag evaluations of any type', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -53,8 +50,6 @@ describe('featureFlagContexts', () => { }) it('should replace existing feature flag evaluation to the current context', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -69,8 +64,6 @@ describe('featureFlagContexts', () => { }) it('should notify the customer data tracker on feature flag evaluation', () => { - const { lifeCycle } = setupBuilder.build() - const updateCustomerDataSpy = spyOn(customerDataTracker, 'updateCustomerData') lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { @@ -89,14 +82,10 @@ describe('featureFlagContexts', () => { * (which seems unlikely) and this event would anyway be rejected by lack of view id */ it('should return undefined when no current view', () => { - setupBuilder.build() - expect(featureFlagContexts.findFeatureFlagEvaluations()).toBeUndefined() }) it('should clear feature flag context on new view', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) @@ -113,8 +102,6 @@ describe('featureFlagContexts', () => { }) it('should return the feature flag context corresponding to the start time', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - lifeCycle.notify(LifeCycleEventType.BEFORE_VIEW_CREATED, { startClocks: relativeToClocks(0 as RelativeTime), } as ViewCreatedEvent) diff --git a/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts b/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts index 3d952ca944..17743a460b 100644 --- a/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts +++ b/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts @@ -1,24 +1,22 @@ import type { Context } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' import type { RumEvent } from '../../rumEvent.types' -import { LifeCycleEventType } from '../lifeCycle' -import type { TestSetupBuilder } from '../../../test' -import { setup } from '../../../test' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' import { RumEventType } from '../../rawRumEvent.types' import { trackViewEventCounts } from './trackViewEventCounts' describe('trackViewEventCounts', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let onChange: () => void beforeEach(() => { onChange = jasmine.createSpy('onChange') - setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackViewEventCounts(lifeCycle, 'view-id', onChange)) + const viewEventCountsTracking = trackViewEventCounts(lifeCycle, 'view-id', onChange) + registerCleanupTask(viewEventCountsTracking.stop) }) it('should track events count', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.ERROR, view: { id: 'view-id' }, @@ -28,8 +26,6 @@ describe('trackViewEventCounts', () => { }) it('should not count child events unrelated to the view', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.ERROR, view: { id: 'unrelated-view-id' }, 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 4e09d78d04..d5c68ef812 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts @@ -1,63 +1,68 @@ import type { RelativeTime } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' import { resetExperimentalFeatures, elapsed, ONE_SECOND } from '@datadog/browser-core' -import type { TestSetupBuilder } from '../../../../test' -import { appendElement, appendText, createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +import { appendElement, appendText, createPerformanceEntry } from '../../../../test' +import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' +import type { RumConfiguration } from '../../configuration' import type { CumulativeLayoutShift } from './trackCumulativeLayoutShift' import { MAX_WINDOW_DURATION, trackCumulativeLayoutShift } from './trackCumulativeLayoutShift' +interface StartCLSTrackingArgs { + viewStart: RelativeTime + isLayoutShiftSupported: boolean +} + describe('trackCumulativeLayoutShift', () => { - let setupBuilder: TestSetupBuilder - let isLayoutShiftSupported: boolean + const lifeCycle = new LifeCycle() let originalSupportedEntryTypes: PropertyDescriptor | undefined let clsCallback: jasmine.Spy<(csl: CumulativeLayoutShift) => void> - let viewStart: RelativeTime - beforeEach(() => { - if ( - !window.PerformanceObserver || - !PerformanceObserver.supportedEntryTypes || - !PerformanceObserver.supportedEntryTypes.includes('layout-shift') - ) { - pending('No LayoutShift API support') + function startCLSTracking( + { viewStart, isLayoutShiftSupported }: StartCLSTrackingArgs = { + viewStart: 0 as RelativeTime, + isLayoutShiftSupported: true, } - + ) { clsCallback = jasmine.createSpy() - viewStart = 0 as RelativeTime - setupBuilder = setup().beforeBuild(({ lifeCycle, configuration }) => - trackCumulativeLayoutShift(configuration, lifeCycle, viewStart, clsCallback) - ) - originalSupportedEntryTypes = Object.getOwnPropertyDescriptor(PerformanceObserver, 'supportedEntryTypes') - isLayoutShiftSupported = true Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', { get: () => (isLayoutShiftSupported ? ['layout-shift'] : []), }) - }) - afterEach(() => { - if (originalSupportedEntryTypes) { - Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', originalSupportedEntryTypes) + const clsTrackingesult = trackCumulativeLayoutShift({} as RumConfiguration, lifeCycle, viewStart, clsCallback) + + registerCleanupTask(() => { + clsTrackingesult.stop() + if (originalSupportedEntryTypes) { + Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', originalSupportedEntryTypes) + } + }) + } + + beforeEach(() => { + if ( + !window.PerformanceObserver || + !PerformanceObserver.supportedEntryTypes || + !PerformanceObserver.supportedEntryTypes.includes('layout-shift') + ) { + pending('No LayoutShift API support') } }) it('should be initialized to 0', () => { - setupBuilder.build() - + startCLSTracking() expect(clsCallback).toHaveBeenCalledOnceWith({ value: 0 }) }) it('should be initialized to undefined if layout-shift is not supported', () => { - isLayoutShiftSupported = false - setupBuilder.build() + startCLSTracking({ viewStart: 0 as RelativeTime, isLayoutShiftSupported: false }) expect(clsCallback).not.toHaveBeenCalled() }) it('should accumulate layout shift values for the first session window', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, startTime: 1 as RelativeTime }), ]) @@ -75,8 +80,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should round the cumulative layout shift value to 4 decimals', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 1.23456789 }), ]) @@ -90,8 +94,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should ignore entries with recent input', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, @@ -106,7 +109,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should create a new session window if the gap is more than 1 second', () => { - const { lifeCycle } = setupBuilder.build() + startCLSTracking() // first session window lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, startTime: 0 as RelativeTime }), @@ -131,8 +134,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should create a new session window if the current session window is more than 5 second', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() for (let i = 1; i <= 7; i++) { lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { @@ -151,8 +153,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should get the max value sessions', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() // first session window: { value: 0.3, time: 1 } lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, startTime: 0 as RelativeTime }), @@ -204,8 +205,9 @@ describe('trackCumulativeLayoutShift', () => { }) it('should get the time from the beginning of the view', () => { - viewStart = 100 as RelativeTime - const { lifeCycle } = setupBuilder.build() + const viewStart = 100 as RelativeTime + startCLSTracking({ viewStart, isLayoutShiftSupported: true }) + const shiftStart = 110 as RelativeTime lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, startTime: shiftStart }), @@ -220,8 +222,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should return the first target element selector amongst all the shifted nodes', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() const textNode = appendText('text') const divElement = appendElement('
') @@ -236,8 +237,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should not return the target element when the element is detached from the DOM before the performance entry event is triggered', () => { - const { lifeCycle } = setupBuilder.build() - + startCLSTracking() // first session window lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { @@ -247,7 +247,6 @@ describe('trackCumulativeLayoutShift', () => { ]) expect(clsCallback.calls.mostRecent().args[0].value).toEqual(0.2) - // second session window // first shift with an element const divElement = appendElement('
') @@ -266,7 +265,7 @@ describe('trackCumulativeLayoutShift', () => { }) it('should get the target element and time of the largest layout shift', () => { - const { lifeCycle } = setupBuilder.build() + startCLSTracking() const divElement = appendElement('
') // first session window: { value: 0.5, time: 1, targetSelector: '#div-element' } 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 8fdb57bba8..1999a785bc 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts @@ -1,40 +1,30 @@ import type { RelativeTime } from '@datadog/browser-core' -import { restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' +import { registerCleanupTask, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import type { TestSetupBuilder } from '../../../../test' -import { createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +import { createPerformanceEntry } from '../../../../test' +import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import type { RumConfiguration } from '../../configuration' import { FCP_MAXIMUM_DELAY, trackFirstContentfulPaint } from './trackFirstContentfulPaint' import { trackFirstHidden } from './trackFirstHidden' describe('trackFirstContentfulPaint', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let fcpCallback: jasmine.Spy<(value: RelativeTime) => void> - let configuration: RumConfiguration - beforeEach(() => { - configuration = {} as RumConfiguration + function startTrackingFCP() { fcpCallback = jasmine.createSpy() - setupBuilder = setup().beforeBuild(({ lifeCycle }) => { - const firstHidden = trackFirstHidden(configuration) - const firstContentfulPaint = trackFirstContentfulPaint(lifeCycle, firstHidden, fcpCallback) - return { - stop() { - firstHidden.stop() - firstContentfulPaint.stop() - }, - } - }) - }) + const firstHidden = trackFirstHidden({} as RumConfiguration) + const firstContentfulPaint = trackFirstContentfulPaint(lifeCycle, firstHidden, fcpCallback) - afterEach(() => { - restorePageVisibility() - }) + registerCleanupTask(() => { + firstHidden.stop() + firstContentfulPaint.stop() + restorePageVisibility() + }) + } it('should provide the first contentful paint timing', () => { - const { lifeCycle } = setupBuilder.build() - + startTrackingFCP() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.PAINT), ]) @@ -45,7 +35,8 @@ describe('trackFirstContentfulPaint', () => { it('should be discarded if the page is hidden', () => { setPageVisibility('hidden') - const { lifeCycle } = setupBuilder.build() + startTrackingFCP() + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.PAINT), ]) @@ -53,8 +44,7 @@ describe('trackFirstContentfulPaint', () => { }) it('should be discarded if it is reported after a long time', () => { - const { lifeCycle } = setupBuilder.build() - + startTrackingFCP() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.PAINT, { startTime: FCP_MAXIMUM_DELAY 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 ebc2f9f116..5a0571447d 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, resetExperimentalFeatures } from '@datadog/browser-core' -import { restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../../../test' -import { appendElement, appendText, createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +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 { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import type { RumConfiguration } from '../../configuration' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' import type { FirstInput } from './trackFirstInput' @@ -10,35 +9,26 @@ import { trackFirstInput } from './trackFirstInput' import { trackFirstHidden } from './trackFirstHidden' describe('firstInputTimings', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let fitCallback: jasmine.Spy<(firstInput: FirstInput) => void> let configuration: RumConfiguration - beforeEach(() => { + function startFirstInputTracking() { configuration = {} as RumConfiguration fitCallback = jasmine.createSpy() - setupBuilder = setup().beforeBuild(({ lifeCycle }) => { - const firstHidden = trackFirstHidden(configuration) - const firstInputTimings = trackFirstInput(lifeCycle, configuration, firstHidden, fitCallback) + const firstHidden = trackFirstHidden(configuration) + const firstInputTimings = trackFirstInput(lifeCycle, configuration, firstHidden, fitCallback) - return { - stop() { - firstHidden.stop() - firstInputTimings.stop() - }, - } + registerCleanupTask(() => { + firstHidden.stop() + firstInputTimings.stop() + restorePageVisibility() }) - }) - - afterEach(() => { - restorePageVisibility() - resetExperimentalFeatures() - }) + } it('should provide the first input timings', () => { - const { lifeCycle } = setupBuilder.build() - + startFirstInputTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT), ]) @@ -51,8 +41,7 @@ describe('firstInputTimings', () => { }) it('should provide the first input target selector', () => { - const { lifeCycle } = setupBuilder.build() - + startFirstInputTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT, { target: appendElement(''), @@ -67,8 +56,7 @@ describe('firstInputTimings', () => { }) it("should not provide the first input target if it's not a DOM element", () => { - const { lifeCycle } = setupBuilder.build() - + startFirstInputTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT, { target: appendText('text'), @@ -84,7 +72,7 @@ describe('firstInputTimings', () => { it('should be discarded if the page is hidden', () => { setPageVisibility('hidden') - const { lifeCycle } = setupBuilder.build() + startFirstInputTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT), @@ -94,8 +82,7 @@ describe('firstInputTimings', () => { }) it('should be adjusted to 0 if the computed value would be negative due to browser timings imprecisions', () => { - const { lifeCycle } = setupBuilder.build() - + startFirstInputTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT, { processingStart: 900 as RelativeTime, 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 841512899c..13ca9c31eb 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInitialViewMetrics.spec.ts @@ -1,36 +1,33 @@ import type { Duration, RelativeTime } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import type { TestSetupBuilder } from '../../../../test' -import { createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +import { createPerformanceEntry } from '../../../../test' +import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import type { RumConfiguration } from '../../configuration' import { trackInitialViewMetrics } from './trackInitialViewMetrics' describe('trackInitialViewMetrics', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let scheduleViewUpdateSpy: jasmine.Spy<() => void> let trackInitialViewMetricsResult: ReturnType let setLoadEventSpy: jasmine.Spy<(loadEvent: Duration) => void> - let configuration: RumConfiguration beforeEach(() => { - configuration = {} as RumConfiguration + const configuration = {} as RumConfiguration scheduleViewUpdateSpy = jasmine.createSpy() setLoadEventSpy = jasmine.createSpy() - setupBuilder = setup().beforeBuild(({ lifeCycle }) => { - trackInitialViewMetricsResult = trackInitialViewMetrics( - lifeCycle, - configuration, - setLoadEventSpy, - scheduleViewUpdateSpy - ) - return trackInitialViewMetricsResult - }) + trackInitialViewMetricsResult = trackInitialViewMetrics( + lifeCycle, + configuration, + setLoadEventSpy, + scheduleViewUpdateSpy + ) + + registerCleanupTask(trackInitialViewMetricsResult.stop) }) it('should merge metrics from various sources', () => { - const { lifeCycle } = setupBuilder.build() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), createPerformanceEntry(RumPerformanceEntryType.PAINT), @@ -56,8 +53,6 @@ describe('trackInitialViewMetrics', () => { }) it('calls the `setLoadEvent` callback when the loadEvent timing is known', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), createPerformanceEntry(RumPerformanceEntryType.PAINT), 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 4031950258..73e4c619f7 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts @@ -1,8 +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 type { TestSetupBuilder } from '../../../../test' -import { appendElement, appendText, createPerformanceEntry, setup } from '../../../../test' +import { appendElement, appendText, createPerformanceEntry } from '../../../../test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' import type { BrowserWindow, @@ -10,8 +9,8 @@ import type { RumPerformanceEventTiming, } from '../../../browser/performanceObservable' import { ViewLoadingType } from '../../../rawRumEvent.types' -import type { LifeCycle } from '../../lifeCycle' -import { LifeCycleEventType } from '../../lifeCycle' +import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' +import type { RumConfiguration } from '../../configuration' import { trackInteractionToNextPaint, trackViewInteractionCount, @@ -20,11 +19,10 @@ import { } from './trackInteractionToNextPaint' describe('trackInteractionToNextPaint', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let interactionCountMock: ReturnType let getInteractionToNextPaint: ReturnType['getInteractionToNextPaint'] let setViewEnd: ReturnType['setViewEnd'] - let viewStart: RelativeTime function newInteraction(lifeCycle: LifeCycle, overrides: Partial) { if (overrides.interactionId) { @@ -34,39 +32,38 @@ describe('trackInteractionToNextPaint', () => { lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [entry]) } - beforeEach(() => { - if (!isInteractionToNextPaintSupported()) { - pending('No INP support') - } + function startINPTracking(viewStart = 0 as RelativeTime) { interactionCountMock = mockInteractionCount() - viewStart = 0 as RelativeTime - setupBuilder = setup().beforeBuild(({ lifeCycle, configuration }) => { - const interactionToNextPaintTracking = trackInteractionToNextPaint( - configuration, - viewStart, - ViewLoadingType.INITIAL_LOAD, - lifeCycle - ) - getInteractionToNextPaint = interactionToNextPaintTracking.getInteractionToNextPaint - setViewEnd = interactionToNextPaintTracking.setViewEnd - - return interactionToNextPaintTracking - }) + const interactionToNextPaintTracking = trackInteractionToNextPaint( + {} as RumConfiguration, + viewStart, + ViewLoadingType.INITIAL_LOAD, + lifeCycle + ) + getInteractionToNextPaint = interactionToNextPaintTracking.getInteractionToNextPaint + setViewEnd = interactionToNextPaintTracking.setViewEnd registerCleanupTask(() => { + interactionToNextPaintTracking.stop resetExperimentalFeatures() interactionCountMock.clear() }) + } + + beforeEach(() => { + if (!isInteractionToNextPaintSupported()) { + pending('No INP support') + } }) it('should return undefined when there are no interactions', () => { - setupBuilder.build() + startINPTracking() expect(getInteractionToNextPaint()).toEqual(undefined) }) it('should ignore entries without interactionId', () => { - const { lifeCycle } = setupBuilder.build() + startINPTracking() newInteraction(lifeCycle, { interactionId: undefined, }) @@ -74,8 +71,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should ignore entries that starts out of the view time bounds', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() setViewEnd(10 as RelativeTime) newInteraction(lifeCycle, { @@ -92,8 +88,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should take into account entries that starts in view time bounds but finish after view end', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() setViewEnd(10 as RelativeTime) newInteraction(lifeCycle, { @@ -109,7 +104,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should cap INP value', () => { - const { lifeCycle } = setupBuilder.build() + startINPTracking() newInteraction(lifeCycle, { interactionId: 1, duration: (MAX_INP_VALUE + 1) as Duration, @@ -124,7 +119,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should return the p98 worst interaction', () => { - const { lifeCycle } = setupBuilder.build() + startINPTracking() for (let index = 1; index <= 100; index++) { newInteraction(lifeCycle, { duration: index as Duration, @@ -140,13 +135,13 @@ describe('trackInteractionToNextPaint', () => { }) it('should return 0 when an interaction happened without generating a performance event (interaction duration below 40ms)', () => { - setupBuilder.build() + startINPTracking() interactionCountMock.setInteractionCount(1 as Duration) // assumes an interaction happened but no PERFORMANCE_ENTRIES_COLLECTED have been triggered expect(getInteractionToNextPaint()).toEqual({ value: 0 as Duration }) }) it('should take first-input entry into account', () => { - const { lifeCycle } = setupBuilder.build() + startINPTracking() newInteraction(lifeCycle, { interactionId: 1, entryType: RumPerformanceEntryType.FIRST_INPUT, @@ -160,8 +155,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should replace the entry in the list of worst interactions when an entry with the same interactionId exist', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() for (let index = 1; index <= 100; index++) { newInteraction(lifeCycle, { duration: index as Duration, @@ -178,8 +172,8 @@ describe('trackInteractionToNextPaint', () => { }) it('should get the time from the beginning of the view', () => { - viewStart = 100 as RelativeTime - const { lifeCycle } = setupBuilder.build() + const viewStart = 100 as RelativeTime + startINPTracking(viewStart) const interactionStart = 110 as RelativeTime newInteraction(lifeCycle, { @@ -192,8 +186,7 @@ describe('trackInteractionToNextPaint', () => { describe('target selector', () => { it('should be returned', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() newInteraction(lifeCycle, { interactionId: 2, target: appendElement(''), @@ -203,8 +196,7 @@ describe('trackInteractionToNextPaint', () => { }) it("should not be returned if it's not a DOM element", () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() newInteraction(lifeCycle, { interactionId: 2, target: appendText('text'), @@ -214,8 +206,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should not be recomputed if the INP has not changed', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() const element = appendElement('') newInteraction(lifeCycle, { interactionId: 1, @@ -235,8 +226,7 @@ describe('trackInteractionToNextPaint', () => { }) it('should be recomputed if the INP has changed', () => { - const { lifeCycle } = setupBuilder.build() - + startINPTracking() newInteraction(lifeCycle, { interactionId: 1, duration: 10 as Duration, 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 43f9e3caa3..8d48db6e5f 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLargestContentfulPaint.spec.ts @@ -1,51 +1,48 @@ import type { RelativeTime } from '@datadog/browser-core' -import { DOM_EVENT, resetExperimentalFeatures } from '@datadog/browser-core' -import { restorePageVisibility, setPageVisibility, createNewEvent } from '@datadog/browser-core/test' +import { DOM_EVENT } from '@datadog/browser-core' +import { + setPageVisibility, + createNewEvent, + restorePageVisibility, + registerCleanupTask, +} from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import type { TestSetupBuilder } from '../../../../test' -import { appendElement, createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +import { appendElement, createPerformanceEntry } 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' describe('trackLargestContentfulPaint', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let lcpCallback: jasmine.Spy<(lcp: LargestContentfulPaint) => void> let eventTarget: Window - let configuration: RumConfiguration + + function startLCPTracking() { + const firstHidden = trackFirstHidden({} as RumConfiguration) + const largestContentfulPaint = trackLargestContentfulPaint( + lifeCycle, + {} as RumConfiguration, + firstHidden, + eventTarget, + lcpCallback + ) + + registerCleanupTask(() => { + firstHidden.stop() + largestContentfulPaint.stop() + restorePageVisibility() + }) + } beforeEach(() => { - configuration = {} as RumConfiguration lcpCallback = jasmine.createSpy() eventTarget = document.createElement('div') as unknown as Window - setupBuilder = setup().beforeBuild(({ lifeCycle }) => { - const firstHidden = trackFirstHidden(configuration) - const largestContentfulPaint = trackLargestContentfulPaint( - lifeCycle, - configuration, - firstHidden, - eventTarget, - lcpCallback - ) - return { - stop() { - firstHidden.stop() - largestContentfulPaint.stop() - }, - } - }) - }) - - afterEach(() => { - restorePageVisibility() - resetExperimentalFeatures() }) it('should provide the largest contentful paint timing', () => { - const { lifeCycle } = setupBuilder.build() - + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), ]) @@ -54,8 +51,7 @@ describe('trackLargestContentfulPaint', () => { }) it('should provide the largest contentful paint target selector', () => { - const { lifeCycle } = setupBuilder.build() - + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, { element: appendElement(''), @@ -66,8 +62,7 @@ describe('trackLargestContentfulPaint', () => { }) it('should be discarded if it is reported after a user interaction', () => { - const { lifeCycle } = setupBuilder.build() - + startLCPTracking() eventTarget.dispatchEvent(createNewEvent(DOM_EVENT.KEY_DOWN, { timeStamp: 1 })) lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ @@ -79,7 +74,7 @@ describe('trackLargestContentfulPaint', () => { it('should be discarded if the page is hidden', () => { setPageVisibility('hidden') - const { lifeCycle } = setupBuilder.build() + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), @@ -89,8 +84,7 @@ describe('trackLargestContentfulPaint', () => { }) it('should be discarded if it is reported after a long time', () => { - const { lifeCycle } = setupBuilder.build() - + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, { startTime: LCP_MAXIMUM_DELAY as RelativeTime, @@ -101,7 +95,7 @@ describe('trackLargestContentfulPaint', () => { }) it('should be discarded if it has a size inferior to the previous LCP entry', () => { - const { lifeCycle } = setupBuilder.build() + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, { startTime: 1 as RelativeTime, @@ -120,7 +114,7 @@ describe('trackLargestContentfulPaint', () => { }) it('should notify multiple times when the size is bigger than the previous entry', () => { - const { lifeCycle } = setupBuilder.build() + startLCPTracking() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT, { startTime: 1 as RelativeTime, 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 3f56bf3975..722ac2cdc0 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts @@ -1,24 +1,23 @@ import type { Duration } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' import { RumPerformanceEntryType } from '../../../browser/performanceObservable' -import type { TestSetupBuilder } from '../../../../test' -import { createPerformanceEntry, setup } from '../../../../test' -import { LifeCycleEventType } from '../../lifeCycle' +import { createPerformanceEntry } from '../../../../test' +import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' import type { NavigationTimings } from './trackNavigationTimings' import { trackNavigationTimings } from './trackNavigationTimings' describe('trackNavigationTimings', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() let navigationTimingsCallback: jasmine.Spy<(timings: NavigationTimings) => void> beforeEach(() => { navigationTimingsCallback = jasmine.createSpy() + const trackNavigationTimingsResult = trackNavigationTimings(lifeCycle, navigationTimingsCallback) - setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackNavigationTimings(lifeCycle, navigationTimingsCallback)) + registerCleanupTask(trackNavigationTimingsResult.stop) }) it('should provide navigation timing', () => { - const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), ]) 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 8f6176a449..06755cee7b 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts @@ -1,8 +1,7 @@ import type { Duration, RelativeTime, Subscription, TimeStamp } from '@datadog/browser-core' import { DOM_EVENT, Observable, isIE } from '@datadog/browser-core' -import { createNewEvent, registerCleanupTask } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../../../test' -import { setup } from '../../../../test' +import type { Clock } from '@datadog/browser-core/test' +import { createNewEvent, mockClock, registerCleanupTask } from '@datadog/browser-core/test' import type { RumConfiguration } from '../../configuration' import type { ScrollMetrics, ScrollValues } from './trackScrollMetrics' import { createScrollValuesObservable, trackScrollMetrics } from './trackScrollMetrics' @@ -49,36 +48,33 @@ describe('createScrollValuesObserver', () => { }) describe('trackScrollMetrics', () => { - let setupBuilder: TestSetupBuilder + let clock: Clock let scrollMetricsCallback: jasmine.Spy<(metrics: ScrollMetrics) => void> const scrollObservable = new Observable() beforeEach(() => { scrollMetricsCallback = jasmine.createSpy() - setupBuilder = setup() - .withFakeClock() - .beforeBuild(({ configuration }) => - trackScrollMetrics( - configuration, - { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, - scrollMetricsCallback, - scrollObservable - ) - ) + clock = mockClock() + trackScrollMetrics( + {} as RumConfiguration, + { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, + scrollMetricsCallback, + scrollObservable + ) }) afterEach(() => { document.body.innerHTML = '' + clock.cleanup() }) const updateScrollValues = (scrollValues: ScrollValues) => { - setupBuilder.clock!.tick(100) + clock.tick(100) scrollObservable.notify(scrollValues) } it('should update scroll height and scroll depth', () => { - setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ maxDepth: 700, @@ -88,7 +84,6 @@ describe('trackScrollMetrics', () => { }) }) it('should update time and scroll height only if it has increased', () => { - setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) updateScrollValues({ scrollDepth: 700, scrollHeight: 1900, scrollTop: 100 }) expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ @@ -100,7 +95,6 @@ describe('trackScrollMetrics', () => { }) it('should update max depth only if it has increased', () => { - setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) updateScrollValues({ scrollDepth: 600, scrollHeight: 2000, scrollTop: 0 }) expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ diff --git a/packages/rum-core/src/domain/vital/vitalCollection.spec.ts b/packages/rum-core/src/domain/vital/vitalCollection.spec.ts index a9b4c68e59..9b0fbd5800 100644 --- a/packages/rum-core/src/domain/vital/vitalCollection.spec.ts +++ b/packages/rum-core/src/domain/vital/vitalCollection.spec.ts @@ -1,30 +1,48 @@ import type { Duration } from '@datadog/browser-core' +import { mockClock, registerCleanupTask, type Clock } from '@datadog/browser-core/test' import { clocksNow } from '@datadog/browser-core' -import type { TestSetupBuilder } from '../../../test' -import { setup } from '../../../test' -import type { RawRumVitalEvent } from '../../rawRumEvent.types' +import { validateRumEventFormat } from '../../../test' +import type { RawRumEvent, RawRumVitalEvent } from '../../rawRumEvent.types' import { VitalType, RumEventType } from '../../rawRumEvent.types' +import type { RawRumEventCollectedData } from '../lifeCycle' +import { LifeCycle, LifeCycleEventType } from '../lifeCycle' +import type { PageStateHistory } from '../contexts/pageStateHistory' import { createVitalInstance, startVitalCollection } from './vitalCollection' +const pageStateHistory: PageStateHistory = { + findAll: () => undefined, + addPageState: () => {}, + stop: () => {}, + wasInPageStateAt: () => false, + wasInPageStateDuringPeriod: () => false, +} + describe('vitalCollection', () => { - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + let rawRumEvents: Array> = [] + let clock: Clock let vitalCollection: ReturnType let wasInPageStateDuringPeriodSpy: jasmine.Spy beforeEach(() => { - setupBuilder = setup() - .withFakeClock() - .beforeBuild(({ lifeCycle, pageStateHistory }) => { - wasInPageStateDuringPeriodSpy = spyOn(pageStateHistory, 'wasInPageStateDuringPeriod') - vitalCollection = startVitalCollection(lifeCycle, pageStateHistory) - }) + clock = mockClock() + wasInPageStateDuringPeriodSpy = spyOn(pageStateHistory, 'wasInPageStateDuringPeriod') + vitalCollection = startVitalCollection(lifeCycle, pageStateHistory) + const eventsSubscription = lifeCycle.subscribe(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, (data) => { + rawRumEvents.push(data) + validateRumEventFormat(data.rawRumEvent) + }) + + registerCleanupTask(() => { + eventsSubscription.unsubscribe() + rawRumEvents = [] + clock.cleanup() + }) }) describe('custom duration', () => { describe('createVitalInstance', () => { it('should create duration vital from a vital instance', () => { - const { clock } = setupBuilder.build() - const cbSpy = jasmine.createSpy() const vital = createVitalInstance(cbSpy, { name: 'foo' }) @@ -53,8 +71,6 @@ describe('vitalCollection', () => { }) it('should create multiple duration vitals from createVitalInstance', () => { - const { clock } = setupBuilder.build() - const cbSpy = jasmine.createSpy() const vital1 = createVitalInstance(cbSpy, { name: 'foo', details: 'component 1' }) @@ -108,8 +124,6 @@ describe('vitalCollection', () => { }) it('should create a vital from start API', () => { - const { rawRumEvents, clock } = setupBuilder.build() - const vital = vitalCollection.startDurationVital({ name: 'foo', context: { foo: 'bar' }, @@ -127,8 +141,6 @@ describe('vitalCollection', () => { }) it('should create a vital from add API', () => { - const { rawRumEvents } = setupBuilder.build() - vitalCollection.addDurationVital({ name: 'foo', type: VitalType.DURATION, @@ -145,7 +157,6 @@ describe('vitalCollection', () => { }) it('should discard a vital for which a frozen state happened', () => { - const { rawRumEvents } = setupBuilder.build() wasInPageStateDuringPeriodSpy.and.returnValue(true) vitalCollection.addDurationVital({ @@ -160,8 +171,6 @@ describe('vitalCollection', () => { }) it('should collect raw rum event from duration vital', () => { - const { rawRumEvents } = setupBuilder.build() - const vital = createVitalInstance(vitalCollection.addDurationVital, { name: 'foo' }) vital.stop() diff --git a/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts b/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts index 6cdaf333aa..cb1db9332b 100644 --- a/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts +++ b/packages/rum-core/src/domain/waitPageActivityEnd.spec.ts @@ -1,12 +1,11 @@ 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 } from '@datadog/browser-core/test' -import type { TestSetupBuilder } from '../../test' -import { createPerformanceEntry, mockPerformanceObserver, setup } from '../../test' +import { mockClock, SPEC_ENDPOINTS } from '@datadog/browser-core/test' +import { createPerformanceEntry, mockPerformanceObserver } from '../../test' import type { RumPerformanceEntry } from '../browser/performanceObservable' import { RumPerformanceEntryType } from '../browser/performanceObservable' -import { LifeCycleEventType } from './lifeCycle' +import { LifeCycle, LifeCycleEventType } from './lifeCycle' import type { RequestCompleteEvent, RequestStartEvent } from './requestCollection' import type { PageActivityEvent, PageActivityEndEvent } from './waitPageActivityEnd' import { @@ -15,6 +14,8 @@ import { doWaitPageActivityEnd, 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 @@ -41,38 +42,57 @@ function eventsCollector() { } } +const RUM_CONFIGURATION: RumConfiguration = { + ...validateAndBuildRumConfiguration({ + clientToken: 'xxx', + applicationId: 'AppId', + trackResources: true, + trackLongTasks: true, + })!, + ...SPEC_ENDPOINTS, +} + describe('createPageActivityObservable', () => { const { events, pushEvent } = eventsCollector() - let setupBuilder: TestSetupBuilder + const lifeCycle = new LifeCycle() + const domMutationObservable = new Observable() let pageActivitySubscription: Subscription let notifyPerformanceEntries: (entries: RumPerformanceEntry[]) => void + function startListeningToPageActivities( + extraConfiguration: Partial = { excludedActivityUrls: [EXCLUDED_FAKE_URL] } + ) { + const pageActivityObservable = createPageActivityObservable(lifeCycle, domMutationObservable, { + ...RUM_CONFIGURATION, + ...extraConfiguration, + }) + pageActivitySubscription = pageActivityObservable.subscribe(pushEvent) + } + beforeEach(() => { ;({ notifyPerformanceEntries } = mockPerformanceObserver()) - setupBuilder = setup() - .withConfiguration({ excludedActivityUrls: [EXCLUDED_FAKE_URL] }) - .beforeBuild(({ lifeCycle, domMutationObservable, configuration }) => { - const pageActivityObservable = createPageActivityObservable(lifeCycle, domMutationObservable, configuration) - pageActivitySubscription = pageActivityObservable.subscribe(pushEvent) - }) + }) + + afterEach(() => { + pageActivitySubscription.unsubscribe() }) it('emits an activity event on dom mutation', () => { - const { domMutationObservable } = setupBuilder.build() + startListeningToPageActivities() domMutationObservable.notify() expect(events).toEqual([{ isBusy: false }]) }) it('emits an activity event on resource collected', () => { - setupBuilder.build() + startListeningToPageActivities() notifyPerformanceEntries([createPerformanceEntry(RumPerformanceEntryType.RESOURCE)]) expect(events).toEqual([{ isBusy: false }]) }) it('does not emit an activity event when a navigation occurs', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), ]) @@ -81,13 +101,13 @@ describe('createPageActivityObservable', () => { it('emits an activity event when `window.open` is used', () => { spyOn(window, 'open') - setupBuilder.build() + startListeningToPageActivities() window.open('toto') expect(events).toEqual([{ isBusy: false }]) }) it('stops emitting activities after calling stop()', () => { - const { domMutationObservable } = setupBuilder.build() + startListeningToPageActivities() domMutationObservable.notify() expect(events).toEqual([{ isBusy: false }]) @@ -101,26 +121,26 @@ describe('createPageActivityObservable', () => { describe('programmatic requests', () => { it('emits an activity event when a request starts', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(10)) expect(events).toEqual([{ isBusy: true }]) }) it('emits an activity event when a request completes', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(10)) lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, makeFakeRequestCompleteEvent(10)) expect(events).toEqual([{ isBusy: true }, { isBusy: false }]) }) it('ignores requests that has started before', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, makeFakeRequestCompleteEvent(10)) expect(events).toEqual([]) }) it('keeps emitting busy events while all requests are not completed', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(10)) lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(11)) lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, makeFakeRequestCompleteEvent(9)) @@ -131,15 +151,13 @@ describe('createPageActivityObservable', () => { describe('excludedActivityUrls', () => { it('ignores resources that should be excluded by configuration', () => { - setupBuilder - .withConfiguration({ - excludedActivityUrls: [ - /^https?:\/\/qux\.com.*/, - 'http://bar.com', - (url: string) => url === 'http://dynamic.com', - ], - }) - .build() + startListeningToPageActivities({ + excludedActivityUrls: [ + /^https?:\/\/qux\.com.*/, + 'http://bar.com', + (url: string) => url === 'http://dynamic.com', + ], + }) notifyPerformanceEntries([ createPerformanceEntry(RumPerformanceEntryType.RESOURCE, { name: 'http://qux.com' }), @@ -151,14 +169,14 @@ describe('createPageActivityObservable', () => { }) it('ignores requests that should be excluded by configuration', () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(10, EXCLUDED_FAKE_URL)) lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, makeFakeRequestCompleteEvent(10, EXCLUDED_FAKE_URL)) expect(events).toEqual([]) }) it("ignored requests don't interfere with pending requests count", () => { - const { lifeCycle } = setupBuilder.build() + startListeningToPageActivities() lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(9)) lifeCycle.notify(LifeCycleEventType.REQUEST_STARTED, makeFakeRequestStartEvent(10, EXCLUDED_FAKE_URL)) lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, makeFakeRequestCompleteEvent(10, EXCLUDED_FAKE_URL)) diff --git a/packages/rum-core/test/testSetupBuilder.ts b/packages/rum-core/test/testSetupBuilder.ts index d1d5d41444..59e6b72e5d 100644 --- a/packages/rum-core/test/testSetupBuilder.ts +++ b/packages/rum-core/test/testSetupBuilder.ts @@ -230,7 +230,7 @@ export function setup(): TestSetupBuilder { return setupBuilder } -function validateRumEventFormat(rawRumEvent: RawRumEvent) { +export function validateRumEventFormat(rawRumEvent: RawRumEvent) { const fakeId = '00000000-aaaa-0000-aaaa-000000000000' const fakeContext: RumContext = { _dd: {