Skip to content

Commit 0c2c609

Browse files
committed
feat(replay): Use vitest instead of jest [WIP]
WIP, use vitest instead of jest. Some notes: * Using jsdom 24 to deal with `TextEncoder` issues * Use vi/jest `*Async` fake timer functions instead of `process.nextTick` * Our `useFakeTimers` module was setting an interval which was causing all sorts of flakes in tests with the updated fake timers library
1 parent 7db3268 commit 0c2c609

Some content is hidden

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

43 files changed

+876
-651
lines changed

packages/replay-internal/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
"clean": "rimraf build sentry-replay-*.tgz",
5454
"fix": "eslint . --format stylish --fix",
5555
"lint": "eslint . --format stylish",
56-
"test": "jest",
57-
"test:watch": "jest --watch",
56+
"test": "vitest",
57+
"test:watch": "vitest --watch",
5858
"yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig"
5959
},
6060
"repository": {
@@ -73,7 +73,10 @@
7373
"@sentry-internal/rrweb": "2.15.0",
7474
"@sentry-internal/rrweb-snapshot": "2.15.0",
7575
"fflate": "^0.8.1",
76-
"jsdom-worker": "^0.2.1"
76+
"jest-matcher-utils": "^29.0.0",
77+
"jsdom": "^24.0.0",
78+
"jsdom-worker": "^0.2.1",
79+
"vitest": "^1.5.3"
7780
},
7881
"dependencies": {
7982
"@sentry-internal/browser-utils": "8.0.0-beta.5",

packages/replay-internal/jest.setup.ts renamed to packages/replay-internal/test.setup.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { TextEncoder } from 'util';
1+
import { vi } from 'vitest';
2+
import type { Assertion, AsymmetricMatchersContaining, Mocked, MockedFunction } from 'vitest';
3+
import { printDiffOrStringify } from 'jest-matcher-utils';
4+
25
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
36
import { getClient } from '@sentry/core';
47
import type { ReplayRecordingData, Transport } from '@sentry/types';
58
import * as SentryUtils from '@sentry/utils';
69

710
import type { ReplayContainer, Session } from './src/types';
811

9-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
10-
(global as any).TextEncoder = TextEncoder;
11-
12-
type MockTransport = jest.MockedFunction<Transport['send']>;
12+
type MockTransport = MockedFunction<Transport['send']>;
1313

14-
jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true);
14+
vi.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true);
1515

1616
type EnvelopeHeader = {
1717
event_id: string;
@@ -36,7 +36,7 @@ type SentReplayExpected = {
3636
};
3737

3838
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39-
const toHaveSameSession = function (received: jest.Mocked<ReplayContainer>, expected: undefined | Session) {
39+
const toHaveSameSession = function (received: Mocked<ReplayContainer>, expected: undefined | Session) {
4040
const pass = this.equals(received.session?.id, expected?.id) as boolean;
4141

4242
const options = {
@@ -47,12 +47,12 @@ const toHaveSameSession = function (received: jest.Mocked<ReplayContainer>, expe
4747
return {
4848
pass,
4949
message: () =>
50-
`${this.utils.matcherHint(
51-
'toHaveSameSession',
52-
undefined,
53-
undefined,
54-
options,
55-
)}\n\n${this.utils.printDiffOrStringify(expected, received.session, 'Expected', 'Received')}`,
50+
`${this.utils.matcherHint('toHaveSameSession', undefined, undefined, options)}\n\n${printDiffOrStringify(
51+
expected,
52+
received.session,
53+
'Expected',
54+
'Received',
55+
)}`,
5656
};
5757
};
5858

@@ -152,7 +152,7 @@ function getReplayCalls(calls: any[][][]): any[][][] {
152152
*/
153153
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
154154
const toHaveSentReplay = function (
155-
_received: jest.Mocked<ReplayContainer>,
155+
_received: Mocked<ReplayContainer>,
156156
expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean },
157157
) {
158158
const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock;
@@ -194,12 +194,7 @@ const toHaveSentReplay = function (
194194
: 'Expected Replay to have been sent, but a request was not attempted'
195195
: `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results
196196
.map(({ key, expectedVal, actualVal }: Result) =>
197-
this.utils.printDiffOrStringify(
198-
expectedVal,
199-
actualVal,
200-
`Expected (key: ${key})`,
201-
`Received (key: ${key})`,
202-
),
197+
printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`),
203198
)
204199
.join('\n')}`,
205200
};
@@ -211,7 +206,7 @@ const toHaveSentReplay = function (
211206
*/
212207
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
213208
const toHaveLastSentReplay = function (
214-
_received: jest.Mocked<ReplayContainer>,
209+
_received: Mocked<ReplayContainer>,
215210
expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean },
216211
) {
217212
const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock;
@@ -235,12 +230,7 @@ const toHaveLastSentReplay = function (
235230
: 'Expected Replay to have last been sent, but a request was not attempted'
236231
: `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results
237232
.map(({ key, expectedVal, actualVal }: Result) =>
238-
this.utils.printDiffOrStringify(
239-
expectedVal,
240-
actualVal,
241-
`Expected (key: ${key})`,
242-
`Received (key: ${key})`,
243-
),
233+
printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`),
244234
)
245235
.join('\n')}`,
246236
};
@@ -254,7 +244,7 @@ expect.extend({
254244

255245
declare global {
256246
// eslint-disable-next-line @typescript-eslint/no-namespace
257-
namespace jest {
247+
namespace vi {
258248
interface AsymmetricMatchers {
259249
toHaveSentReplay(expected?: SentReplayExpected): void;
260250
toHaveLastSentReplay(expected?: SentReplayExpected): void;
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
1+
import { vi } from 'vitest';
2+
13
import { EventType } from '@sentry-internal/rrweb';
24

35
import type { RecordingEvent } from '../../src/types';
46
import { addEvent } from '../../src/util/addEvent';
57
import { resetSdkMock } from '../mocks/resetSdkMock';
68
import { useFakeTimers } from '../utils/use-fake-timers';
9+
import { saveSession } from '../../src/session/saveSession';
710

811
useFakeTimers();
912

13+
vi.mock('../../src/session/saveSession', () => {
14+
return {
15+
saveSession: vi.fn(),
16+
};
17+
});
18+
1019
describe('Integration | autoSaveSession', () => {
1120
afterEach(() => {
12-
jest.clearAllMocks();
21+
vi.clearAllMocks();
1322
});
1423

1524
test.each([
1625
['with stickySession=true', true, 1],
1726
['with stickySession=false', false, 0],
1827
])('%s', async (_: string, stickySession: boolean, addSummand: number) => {
19-
const saveSessionSpy = jest.fn();
20-
21-
jest.mock('../../src/session/saveSession', () => {
22-
return {
23-
saveSession: saveSessionSpy,
24-
};
25-
});
26-
2728
const { replay } = await resetSdkMock({
2829
replayOptions: {
2930
stickySession,
3031
},
3132
});
3233

3334
// Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase
34-
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3);
35+
expect(saveSession).toHaveBeenCalledTimes(addSummand * 3);
3536

3637
replay['_updateSessionActivity']();
3738

38-
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4);
39+
expect(saveSession).toHaveBeenCalledTimes(addSummand * 4);
3940

4041
// In order for runFlush to actually do something, we need to add an event
4142
const event = {
@@ -48,8 +49,8 @@ describe('Integration | autoSaveSession', () => {
4849

4950
addEvent(replay, event);
5051

51-
await replay['_runFlush']();
52+
await Promise.all([replay['_runFlush'](), vi.runAllTimersAsync()]);
5253

53-
expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5);
54+
expect(saveSession).toHaveBeenCalledTimes(addSummand * 5);
5455
});
5556
});

packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { vi } from 'vitest';
2+
import type { MockedFunction, MockInstance } from 'vitest';
3+
14
import * as SentryBrowserUtils from '@sentry-internal/browser-utils';
25
import * as SentryCore from '@sentry/core';
36
import type { Transport } from '@sentry/types';
@@ -14,24 +17,19 @@ import { useFakeTimers } from '../utils/use-fake-timers';
1417

1518
useFakeTimers();
1619

17-
async function advanceTimers(time: number) {
18-
jest.advanceTimersByTime(time);
19-
await new Promise(process.nextTick);
20-
}
21-
22-
type MockTransportSend = jest.MockedFunction<Transport['send']>;
20+
type MockTransportSend = MockedFunction<Transport['send']>;
2321

2422
describe('Integration | beforeAddRecordingEvent', () => {
2523
let replay: ReplayContainer;
2624
let integration: Replay;
2725
let mockTransportSend: MockTransportSend;
28-
let mockSendReplayRequest: jest.SpyInstance<any>;
26+
let mockSendReplayRequest: MockInstance<any>;
2927
let domHandler: DomHandler;
3028
const { record: mockRecord } = mockRrweb();
3129

3230
beforeAll(async () => {
33-
jest.setSystemTime(new Date(BASE_TIMESTAMP));
34-
jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => {
31+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
32+
vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => {
3533
domHandler = handler;
3634
});
3735

@@ -69,14 +67,14 @@ describe('Integration | beforeAddRecordingEvent', () => {
6967
},
7068
}));
7169

72-
mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest');
70+
mockSendReplayRequest = vi.spyOn(SendReplayRequest, 'sendReplayRequest');
7371

74-
jest.runAllTimers();
72+
vi.runAllTimers();
7573
mockTransportSend = SentryCore.getClient()?.getTransport()?.send as MockTransportSend;
7674
});
7775

7876
beforeEach(() => {
79-
jest.setSystemTime(new Date(BASE_TIMESTAMP));
77+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
8078
mockRecord.takeFullSnapshot.mockClear();
8179
mockTransportSend.mockClear();
8280

@@ -90,9 +88,9 @@ describe('Integration | beforeAddRecordingEvent', () => {
9088
});
9189

9290
afterEach(async () => {
93-
jest.runAllTimers();
91+
vi.runAllTimers();
9492
await new Promise(process.nextTick);
95-
jest.setSystemTime(new Date(BASE_TIMESTAMP));
93+
vi.setSystemTime(new Date(BASE_TIMESTAMP));
9694
clearSession(replay);
9795
});
9896

@@ -106,7 +104,7 @@ describe('Integration | beforeAddRecordingEvent', () => {
106104
event: new Event('click'),
107105
});
108106

109-
await advanceTimers(5000);
107+
await vi.runAllTimersAsync();
110108

111109
expect(replay).toHaveLastSentReplay({
112110
recordingPayloadHeader: { segment_id: 0 },
@@ -135,8 +133,7 @@ describe('Integration | beforeAddRecordingEvent', () => {
135133

136134
integration.start();
137135

138-
jest.runAllTimers();
139-
await new Promise(process.nextTick);
136+
await vi.runAllTimersAsync();
140137
expect(replay).toHaveLastSentReplay({
141138
recordingPayloadHeader: { segment_id: 0 },
142139
recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]),
@@ -174,7 +171,7 @@ describe('Integration | beforeAddRecordingEvent', () => {
174171
]),
175172
);
176173

177-
jest.runAllTimers();
174+
vi.runAllTimers();
178175
await new Promise(process.nextTick);
179176

180177
expect(replay).not.toHaveLastSentReplay();

0 commit comments

Comments
 (0)