Skip to content

Commit 1cf0ae0

Browse files
authored
test(replay): Streamline replay test naming & modules (#6656)
1 parent 5a1339e commit 1cf0ae0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2246
-2119
lines changed

packages/replay/src/replay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent';
1818
import { handleHistorySpanListener } from './coreHandlers/handleHistory';
1919
import { handleXhrSpanListener } from './coreHandlers/handleXhr';
2020
import { setupPerformanceObserver } from './coreHandlers/performanceObserver';
21-
import { createPerformanceEntries } from './createPerformanceEntry';
2221
import { createEventBuffer } from './eventBuffer';
2322
import { getSession } from './session/getSession';
2423
import { saveSession } from './session/saveSession';
@@ -39,6 +38,7 @@ import type {
3938
import { addEvent } from './util/addEvent';
4039
import { addMemoryEntry } from './util/addMemoryEntry';
4140
import { createBreadcrumb } from './util/createBreadcrumb';
41+
import { createPerformanceEntries } from './util/createPerformanceEntries';
4242
import { createPerformanceSpans } from './util/createPerformanceSpans';
4343
import { createRecordingData } from './util/createRecordingData';
4444
import { createReplayEnvelope } from './util/createReplayEnvelope';

packages/replay/src/createPerformanceEntry.ts renamed to packages/replay/src/util/createPerformanceEntries.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { browserPerformanceTimeOrigin } from '@sentry/utils';
22
import { record } from 'rrweb';
33

4-
import { WINDOW } from './constants';
4+
import { WINDOW } from '../constants';
55
import type {
66
AllPerformanceEntry,
77
PerformanceNavigationTiming,
88
PerformancePaintTiming,
99
ReplayPerformanceEntry,
10-
} from './types';
10+
} from '../types';
1111

1212
// Map entryType -> function to normalize data for event
1313
// @ts-ignore TODO: entry type does not fit the create* functions entry type
@@ -18,7 +18,7 @@ const ENTRY_TYPES: Record<string, (entry: AllPerformanceEntry) => null | ReplayP
1818
// @ts-ignore TODO: entry type does not fit the create* functions entry type
1919
navigation: createNavigationEntry,
2020
// @ts-ignore TODO: entry type does not fit the create* functions entry type
21-
'largest-contentful-paint': createLargestContentfulPaint,
21+
['largest-contentful-paint']: createLargestContentfulPaint,
2222
};
2323

2424
/**
@@ -42,7 +42,9 @@ function getAbsoluteTime(time: number): number {
4242
return ((browserPerformanceTimeOrigin || WINDOW.performance.timeOrigin) + time) / 1000;
4343
}
4444

45-
function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry {
45+
// TODO: type definition!
46+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
47+
function createPaintEntry(entry: PerformancePaintTiming) {
4648
const { duration, entryType, name, startTime } = entry;
4749

4850
const start = getAbsoluteTime(startTime);
@@ -54,7 +56,9 @@ function createPaintEntry(entry: PerformancePaintTiming): ReplayPerformanceEntry
5456
};
5557
}
5658

57-
function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerformanceEntry | null {
59+
// TODO: type definition!
60+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
61+
function createNavigationEntry(entry: PerformanceNavigationTiming) {
5862
// TODO: There looks to be some more interesting bits in here (domComplete, domContentLoaded)
5963
const { entryType, name, duration, domComplete, startTime, transferSize, type } = entry;
6064

@@ -75,7 +79,9 @@ function createNavigationEntry(entry: PerformanceNavigationTiming): ReplayPerfor
7579
};
7680
}
7781

78-
function createResourceEntry(entry: PerformanceResourceTiming): ReplayPerformanceEntry | null {
82+
// TODO: type definition!
83+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
84+
function createResourceEntry(entry: PerformanceResourceTiming) {
7985
const { entryType, initiatorType, name, responseEnd, startTime, encodedBodySize, transferSize } = entry;
8086

8187
// Core SDK handles these
@@ -95,9 +101,9 @@ function createResourceEntry(entry: PerformanceResourceTiming): ReplayPerformanc
95101
};
96102
}
97103

98-
function createLargestContentfulPaint(
99-
entry: PerformanceEntry & { size: number; element: Node },
100-
): ReplayPerformanceEntry {
104+
// TODO: type definition!
105+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
106+
function createLargestContentfulPaint(entry: PerformanceEntry & { size: number; element: Node }) {
101107
const { duration, entryType, startTime, size } = entry;
102108

103109
const start = getAbsoluteTime(startTime);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { EventType } from 'rrweb';
2+
3+
import type { RecordingEvent } from '../../src/types';
4+
import { addEvent } from '../../src/util/addEvent';
5+
import { resetSdkMock } from '../mocks/resetSdkMock';
6+
import { useFakeTimers } from '../utils/use-fake-timers';
7+
8+
useFakeTimers();
9+
10+
describe('Integration | autoSaveSession', () => {
11+
afterEach(() => {
12+
jest.clearAllMocks();
13+
});
14+
15+
test.each([
16+
['with stickySession=true', true, 1],
17+
['with stickySession=false', false, 0],
18+
])('%s', async (_: string, stickySession: boolean, addSummand: number) => {
19+
let saveSessionSpy;
20+
21+
jest.mock('../../src/session/saveSession', () => {
22+
saveSessionSpy = jest.fn();
23+
24+
return {
25+
saveSession: saveSessionSpy,
26+
};
27+
});
28+
29+
const { replay } = await resetSdkMock({
30+
replayOptions: {
31+
stickySession,
32+
},
33+
});
34+
35+
// Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase
36+
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3);
37+
38+
replay.updateSessionActivity();
39+
40+
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4);
41+
42+
// In order for runFlush to actually do something, we need to add an event
43+
const event = {
44+
type: EventType.Custom,
45+
data: {
46+
tag: 'test custom',
47+
},
48+
timestamp: new Date().valueOf(),
49+
} as RecordingEvent;
50+
51+
addEvent(replay, event);
52+
53+
await replay.runFlush();
54+
55+
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5);
56+
});
57+
});

packages/replay/test/unit/index-handleGlobalEvent.test.ts renamed to packages/replay/test/integration/coreHandlers/handleGlobalEvent.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import { getCurrentHub } from '@sentry/core';
22
import { Event } from '@sentry/types';
33

4-
import { REPLAY_EVENT_NAME } from '../../src/constants';
5-
import { handleGlobalEventListener } from '../../src/coreHandlers/handleGlobalEvent';
6-
import { overwriteRecordDroppedEvent, restoreRecordDroppedEvent } from '../../src/util/monkeyPatchRecordDroppedEvent';
7-
import { ReplayContainer } from './../../src/replay';
8-
import { Error } from './../fixtures/error';
9-
import { Transaction } from './../fixtures/transaction';
10-
import { resetSdkMock } from './../mocks/resetSdkMock';
11-
import { useFakeTimers } from './../utils/use-fake-timers';
4+
import { REPLAY_EVENT_NAME } from '../../../src/constants';
5+
import { handleGlobalEventListener } from '../../../src/coreHandlers/handleGlobalEvent';
6+
import { ReplayContainer } from '../../../src/replay';
7+
import {
8+
overwriteRecordDroppedEvent,
9+
restoreRecordDroppedEvent,
10+
} from '../../../src/util/monkeyPatchRecordDroppedEvent';
11+
import { Error } from '../../fixtures/error';
12+
import { Transaction } from '../../fixtures/transaction';
13+
import { resetSdkMock } from '../../mocks/resetSdkMock';
14+
import { useFakeTimers } from '../../utils/use-fake-timers';
1215

1316
useFakeTimers();
1417
let replay: ReplayContainer;
1518

16-
describe('handleGlobalEvent', () => {
19+
describe('Integration | coreHandlers | handleGlobalEvent', () => {
1720
beforeEach(async () => {
1821
({ replay } = await resetSdkMock({
1922
replayOptions: {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getCurrentHub } from '@sentry/core';
2+
3+
import * as HandleScope from '../../../src/coreHandlers/handleScope';
4+
import { mockSdk } from './../../index';
5+
6+
jest.useFakeTimers();
7+
8+
describe('Integration | coreHandlers | handleScope', () => {
9+
beforeAll(async function () {
10+
await mockSdk();
11+
jest.runAllTimers();
12+
});
13+
14+
it('returns a breadcrumb only if last breadcrumb has changed', function () {
15+
const mockHandleScope = jest.spyOn(HandleScope, 'handleScope');
16+
getCurrentHub().getScope()?.addBreadcrumb({ message: 'testing' });
17+
18+
expect(mockHandleScope).toHaveBeenCalledTimes(1);
19+
expect(mockHandleScope).toHaveReturnedWith(expect.objectContaining({ message: 'testing' }));
20+
21+
mockHandleScope.mockClear();
22+
23+
// This will trigger breadcrumb/scope listener, but handleScope should return
24+
// null because breadcrumbs has not changed
25+
getCurrentHub().getScope()?.setUser({ email: '[email protected]' });
26+
expect(mockHandleScope).toHaveBeenCalledTimes(1);
27+
expect(mockHandleScope).toHaveReturnedWith(null);
28+
});
29+
});

packages/replay/test/unit/index-errorSampleRate.test.ts renamed to packages/replay/test/integration/errorSampleRate.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { captureException } from '@sentry/core';
22

33
import { DEFAULT_FLUSH_MIN_DELAY, REPLAY_SESSION_KEY, VISIBILITY_CHANGE_TIMEOUT, WINDOW } from '../../src/constants';
4+
import { ReplayContainer } from '../../src/replay';
45
import { addEvent } from '../../src/util/addEvent';
6+
import { PerformanceEntryResource } from '../fixtures/performanceEntry/resource';
7+
import { BASE_TIMESTAMP, RecordMock } from '../index';
8+
import { resetSdkMock } from '../mocks/resetSdkMock';
9+
import { DomHandler } from '../types';
510
import { clearSession } from '../utils/clearSession';
6-
import { ReplayContainer } from './../../src/replay';
7-
import { PerformanceEntryResource } from './../fixtures/performanceEntry/resource';
8-
import { BASE_TIMESTAMP, RecordMock } from './../index';
9-
import { resetSdkMock } from './../mocks/resetSdkMock';
10-
import { DomHandler } from './../types';
11-
import { useFakeTimers } from './../utils/use-fake-timers';
11+
import { useFakeTimers } from '../utils/use-fake-timers';
1212

1313
useFakeTimers();
1414

@@ -17,7 +17,7 @@ async function advanceTimers(time: number) {
1717
await new Promise(process.nextTick);
1818
}
1919

20-
describe('Replay (errorSampleRate)', () => {
20+
describe('Integration | errorSampleRate', () => {
2121
let replay: ReplayContainer;
2222
let mockRecord: RecordMock;
2323
let domHandler: DomHandler;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { getCurrentHub } from '@sentry/core';
2+
import { Event, Hub, Scope } from '@sentry/types';
3+
4+
import { BASE_TIMESTAMP } from '..';
5+
import { resetSdkMock } from '../mocks/resetSdkMock';
6+
import { useFakeTimers } from '../utils/use-fake-timers';
7+
8+
useFakeTimers();
9+
10+
describe('Integration | eventProcessors', () => {
11+
let hub: Hub;
12+
let scope: Scope;
13+
14+
beforeEach(() => {
15+
hub = getCurrentHub();
16+
scope = hub.pushScope();
17+
});
18+
19+
afterEach(() => {
20+
hub.popScope();
21+
jest.resetAllMocks();
22+
});
23+
24+
it('handles event processors properly', async () => {
25+
const MUTATED_TIMESTAMP = BASE_TIMESTAMP + 3000;
26+
27+
const { mockRecord } = await resetSdkMock({
28+
replayOptions: {
29+
stickySession: false,
30+
},
31+
});
32+
33+
const client = hub.getClient()!;
34+
35+
jest.runAllTimers();
36+
const mockTransportSend = jest.spyOn(client.getTransport()!, 'send');
37+
mockTransportSend.mockReset();
38+
39+
const handler1 = jest.fn((event: Event): Event | null => {
40+
event.timestamp = MUTATED_TIMESTAMP;
41+
42+
return event;
43+
});
44+
45+
const handler2 = jest.fn((): Event | null => {
46+
return null;
47+
});
48+
49+
scope.addEventProcessor(handler1);
50+
51+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
52+
53+
mockRecord._emitter(TEST_EVENT);
54+
jest.runAllTimers();
55+
jest.advanceTimersByTime(1);
56+
await new Promise(process.nextTick);
57+
58+
expect(mockTransportSend).toHaveBeenCalledTimes(1);
59+
60+
scope.addEventProcessor(handler2);
61+
62+
const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
63+
64+
mockRecord._emitter(TEST_EVENT2);
65+
jest.runAllTimers();
66+
jest.advanceTimersByTime(1);
67+
await new Promise(process.nextTick);
68+
69+
expect(mockTransportSend).toHaveBeenCalledTimes(1);
70+
71+
expect(handler1).toHaveBeenCalledTimes(2);
72+
expect(handler2).toHaveBeenCalledTimes(1);
73+
74+
// This receives an envelope, which is a deeply nested array
75+
// We only care about the fact that the timestamp was mutated
76+
expect(mockTransportSend).toHaveBeenCalledWith(
77+
expect.arrayContaining([
78+
expect.arrayContaining([expect.arrayContaining([expect.objectContaining({ timestamp: MUTATED_TIMESTAMP })])]),
79+
]),
80+
);
81+
});
82+
});

0 commit comments

Comments
 (0)