diff --git a/packages/replay-internal/.eslintrc.js b/packages/replay-internal/.eslintrc.js index 6af926975df0..dc39a10b354b 100644 --- a/packages/replay-internal/.eslintrc.js +++ b/packages/replay-internal/.eslintrc.js @@ -10,7 +10,7 @@ module.exports = { files: ['src/**/*.ts'], }, { - files: ['jest.setup.ts', 'jest.config.ts'], + files: ['test.setup.ts', 'vitest.config.ts'], parserOptions: { project: ['tsconfig.test.json'], }, diff --git a/packages/replay-internal/jest.config.ts b/packages/replay-internal/jest.config.ts deleted file mode 100644 index 90a3cf471f8d..000000000000 --- a/packages/replay-internal/jest.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Config } from '@jest/types'; -import { jsWithTs as jsWithTsPreset } from 'ts-jest/presets'; - -export default async (): Promise => { - return { - ...jsWithTsPreset, - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.test.json', - }, - __DEBUG_BUILD__: true, - }, - setupFilesAfterEnv: ['./jest.setup.ts'], - testEnvironment: 'jsdom', - testMatch: ['/test/**/*(*.)@(spec|test).ts'], - }; -}; diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index f47631ba2cad..e10f2927990f 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -51,10 +51,12 @@ "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", "circularDepCheck": "madge --circular src/index.ts", "clean": "rimraf build sentry-replay-*.tgz", - "fix": "eslint . --format stylish --fix", + "fix": "run-s fix:biome fix:eslint", + "fix:eslint": "eslint . --format stylish --fix", + "fix:biome": "biome check --apply .", "lint": "eslint . --format stylish", - "test": "jest", - "test:watch": "jest --watch", + "test": "vitest", + "test:watch": "vitest --watch", "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push --sig" }, "repository": { @@ -73,6 +75,7 @@ "@sentry-internal/rrweb": "2.15.0", "@sentry-internal/rrweb-snapshot": "2.15.0", "fflate": "^0.8.1", + "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.2.1" }, "dependencies": { diff --git a/packages/replay-internal/jest.setup.ts b/packages/replay-internal/test.setup.ts similarity index 80% rename from packages/replay-internal/jest.setup.ts rename to packages/replay-internal/test.setup.ts index eaba8ee05179..05a762e60d50 100644 --- a/packages/replay-internal/jest.setup.ts +++ b/packages/replay-internal/test.setup.ts @@ -1,4 +1,7 @@ -import { TextEncoder } from 'util'; +import { printDiffOrStringify } from 'jest-matcher-utils'; +import { vi } from 'vitest'; +import type { Mocked, MockedFunction } from 'vitest'; + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getClient } from '@sentry/core'; import type { ReplayRecordingData, Transport } from '@sentry/types'; @@ -6,12 +9,9 @@ import * as SentryUtils from '@sentry/utils'; import type { ReplayContainer, Session } from './src/types'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(global as any).TextEncoder = TextEncoder; - -type MockTransport = jest.MockedFunction; +type MockTransport = MockedFunction; -jest.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); +vi.spyOn(SentryUtils, 'isBrowser').mockImplementation(() => true); type EnvelopeHeader = { event_id: string; @@ -36,7 +36,7 @@ type SentReplayExpected = { }; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSameSession = function (received: jest.Mocked, expected: undefined | Session) { +const toHaveSameSession = function (received: Mocked, expected: undefined | Session) { const pass = this.equals(received.session?.id, expected?.id) as boolean; const options = { @@ -47,12 +47,12 @@ const toHaveSameSession = function (received: jest.Mocked, expe return { pass, message: () => - `${this.utils.matcherHint( - 'toHaveSameSession', - undefined, - undefined, - options, - )}\n\n${this.utils.printDiffOrStringify(expected, received.session, 'Expected', 'Received')}`, + `${this.utils.matcherHint('toHaveSameSession', undefined, undefined, options)}\n\n${printDiffOrStringify( + expected, + received.session, + 'Expected', + 'Received', + )}`, }; }; @@ -101,6 +101,7 @@ function checkCallForSentReplay( : (expected as SentReplayExpected); if (isObjectContaining) { + // eslint-disable-next-line no-console console.warn('`expect.objectContaining` is unnecessary when using the `toHaveSentReplay` matcher'); } @@ -152,7 +153,7 @@ function getReplayCalls(calls: any[][][]): any[][][] { */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const toHaveSentReplay = function ( - _received: jest.Mocked, + _received: Mocked, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, ) { const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; @@ -194,12 +195,7 @@ const toHaveSentReplay = function ( : 'Expected Replay to have been sent, but a request was not attempted' : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), ) .join('\n')}`, }; @@ -211,7 +207,7 @@ const toHaveSentReplay = function ( */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const toHaveLastSentReplay = function ( - _received: jest.Mocked, + _received: Mocked, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, ) { const { calls } = (getClient()?.getTransport()?.send as MockTransport).mock; @@ -235,12 +231,7 @@ const toHaveLastSentReplay = function ( : 'Expected Replay to have last been sent, but a request was not attempted' : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results .map(({ key, expectedVal, actualVal }: Result) => - this.utils.printDiffOrStringify( - expectedVal, - actualVal, - `Expected (key: ${key})`, - `Received (key: ${key})`, - ), + printDiffOrStringify(expectedVal, actualVal, `Expected (key: ${key})`, `Received (key: ${key})`), ) .join('\n')}`, }; @@ -252,18 +243,13 @@ expect.extend({ toHaveLastSentReplay, }); -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface AsymmetricMatchers { - toHaveSentReplay(expected?: SentReplayExpected): void; - toHaveLastSentReplay(expected?: SentReplayExpected): void; - toHaveSameSession(expected: undefined | Session): void; - } - interface Matchers { - toHaveSentReplay(expected?: SentReplayExpected): R; - toHaveLastSentReplay(expected?: SentReplayExpected): R; - toHaveSameSession(expected: undefined | Session): R; - } - } +interface CustomMatchers { + toHaveSentReplay(expected?: SentReplayExpected): R; + toHaveLastSentReplay(expected?: SentReplayExpected): R; + toHaveSameSession(expected: undefined | Session): R; +} + +declare module 'vitest' { + type Assertion = CustomMatchers; + type AsymmetricMatchersContaining = CustomMatchers; } diff --git a/packages/replay-internal/test/integration/autoSaveSession.test.ts b/packages/replay-internal/test/integration/autoSaveSession.test.ts index 6fc0539c771d..7fb3951756ae 100644 --- a/packages/replay-internal/test/integration/autoSaveSession.test.ts +++ b/packages/replay-internal/test/integration/autoSaveSession.test.ts @@ -1,5 +1,8 @@ +import { vi } from 'vitest'; + import { EventType } from '@sentry-internal/rrweb'; +import { saveSession } from '../../src/session/saveSession'; import type { RecordingEvent } from '../../src/types'; import { addEvent } from '../../src/util/addEvent'; import { resetSdkMock } from '../mocks/resetSdkMock'; @@ -7,23 +10,21 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); +vi.mock('../../src/session/saveSession', () => { + return { + saveSession: vi.fn(), + }; +}); + describe('Integration | autoSaveSession', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); test.each([ ['with stickySession=true', true, 1], ['with stickySession=false', false, 0], ])('%s', async (_: string, stickySession: boolean, addSummand: number) => { - const saveSessionSpy = jest.fn(); - - jest.mock('../../src/session/saveSession', () => { - return { - saveSession: saveSessionSpy, - }; - }); - const { replay } = await resetSdkMock({ replayOptions: { stickySession, @@ -31,11 +32,11 @@ describe('Integration | autoSaveSession', () => { }); // Initially called up to three times: once for start, then once for replay.updateSessionActivity & once for segmentId increase - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 3); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 3); replay['_updateSessionActivity'](); - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 4); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 4); // In order for runFlush to actually do something, we need to add an event const event = { @@ -48,8 +49,8 @@ describe('Integration | autoSaveSession', () => { addEvent(replay, event); - await replay['_runFlush'](); + await Promise.all([replay['_runFlush'](), vi.runAllTimersAsync()]); - expect(saveSessionSpy).toHaveBeenCalledTimes(addSummand * 5); + expect(saveSession).toHaveBeenCalledTimes(addSummand * 5); }); }); diff --git a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts index a8331149838b..7f8495375864 100644 --- a/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts +++ b/packages/replay-internal/test/integration/beforeAddRecordingEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryCore from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -14,24 +17,19 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = MockedFunction; describe('Integration | beforeAddRecordingEvent', () => { let replay: ReplayContainer; let integration: Replay; let mockTransportSend: MockTransportSend; - let mockSendReplayRequest: jest.SpyInstance; + let mockSendReplayRequest: MockInstance; let domHandler: DomHandler; const { record: mockRecord } = mockRrweb(); beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); @@ -69,14 +67,14 @@ describe('Integration | beforeAddRecordingEvent', () => { }, })); - mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + mockSendReplayRequest = vi.spyOn(SendReplayRequest, 'sendReplayRequest'); - jest.runAllTimers(); + vi.runAllTimers(); mockTransportSend = SentryCore.getClient()?.getTransport()?.send as MockTransportSend; }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); mockTransportSend.mockClear(); @@ -90,9 +88,9 @@ describe('Integration | beforeAddRecordingEvent', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); clearSession(replay); }); @@ -106,7 +104,7 @@ describe('Integration | beforeAddRecordingEvent', () => { event: new Event('click'), }); - await advanceTimers(5000); + await vi.runAllTimersAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -135,8 +133,7 @@ describe('Integration | beforeAddRecordingEvent', () => { integration.start(); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), @@ -174,8 +171,7 @@ describe('Integration | beforeAddRecordingEvent', () => { ]), ); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveLastSentReplay(); expect(replay.isEnabled()).toBe(true); diff --git a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts index 03f4c236b81d..afab6ac6030a 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { getClient } from '@sentry/core'; import type { ErrorEvent, Event } from '@sentry/types'; @@ -79,8 +82,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); // Does not affect error session - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(Array.from(replay.getContext().errorIds)).toEqual([]); expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); @@ -152,9 +154,11 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; + expect(mockSend).toHaveBeenCalledTimes(0); const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); + await vi.runOnlyPendingTimersAsync(); const handler = handleAfterSendEvent(replay); @@ -164,13 +168,13 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); - // Send twice, one for the error & one right after for the session conversion + // handleAfterSendEvent calls `sendBufferedReplayOrFlush`, which + // flushes immediately but also calls `startRecording` which eventually + // triggers another flush after flush delay. + await vi.runOnlyPendingTimersAsync(); expect(mockSend).toHaveBeenCalledTimes(1); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runOnlyPendingTimersAsync(); expect(mockSend).toHaveBeenCalledTimes(2); // This is removed now, because it has been converted to a "session" session @@ -191,7 +195,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1' }); @@ -203,8 +207,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Send once for the regular session sending expect(mockSend).toHaveBeenCalledTimes(1); @@ -225,7 +228,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const profileEvent: Event = { type: 'profile' }; const replayEvent: Event = { type: 'replay_event' }; @@ -239,8 +242,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockSend).toHaveBeenCalledTimes(0); expect(Array.from(replay.getContext().errorIds)).toEqual([]); @@ -260,7 +262,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1' }); @@ -272,8 +274,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -294,7 +295,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1: ErrorEvent = { event_id: 'err1', type: undefined }; @@ -306,8 +307,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -328,7 +328,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1', message: UNABLE_TO_SEND_REPLAY }); @@ -340,8 +340,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(Array.from(replay.getContext().errorIds)).toEqual(['err1']); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); // Remains in buffer mode & without flushing expect(mockSend).toHaveBeenCalledTimes(0); @@ -362,7 +361,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); @@ -372,8 +371,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { replay['_isEnabled'] = false; - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockSend).toHaveBeenCalledTimes(0); }); @@ -382,7 +380,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { const error1 = Error({ event_id: 'err1', tags: { replayId: 'replayid1' } }); const error2 = Error({ event_id: 'err2', tags: { replayId: 'replayid1' } }); - const beforeErrorSampling = jest.fn(event => event === error2); + const beforeErrorSampling = vi.fn(event => event === error2); ({ replay } = await resetSdkMock({ replayOptions: { @@ -395,7 +393,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { }, })); - const mockSend = getClient()!.getTransport()!.send as unknown as jest.SpyInstance; + const mockSend = getClient()!.getTransport()!.send as unknown as MockInstance; const handler = handleAfterSendEvent(replay); @@ -403,8 +401,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(error1, { statusCode: 200 }); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(beforeErrorSampling).toHaveBeenCalledTimes(1); @@ -415,8 +412,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(error2, { statusCode: 200 }); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(beforeErrorSampling).toHaveBeenCalledTimes(2); diff --git a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts index a1462b974711..8cd9fda46247 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleBeforeSendEvent.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { handleBeforeSendEvent } from '../../../src/coreHandlers/handleBeforeSendEvent'; import type { ReplayContainer } from '../../../src/replay'; import { Error } from '../../fixtures/error'; @@ -24,7 +26,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { })); const handler = handleBeforeSendEvent(replay); - const addBreadcrumbSpy = jest.spyOn(replay, 'throttledAddEvent'); + const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); error.exception.values[0].value = @@ -58,7 +60,7 @@ describe('Integration | coreHandlers | handleBeforeSendEvent', () => { })); const handler = handleBeforeSendEvent(replay); - const addBreadcrumbSpy = jest.spyOn(replay, 'throttledAddEvent'); + const addBreadcrumbSpy = vi.spyOn(replay, 'throttledAddEvent'); const error = Error(); error.exception.values[0].value = 'https://reactjs.org/docs/error-decoder.html?invariant=423'; diff --git a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts index 5200294808db..ebe43c25eb98 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleGlobalEvent.test.ts @@ -191,7 +191,7 @@ describe('Integration | coreHandlers | handleGlobalEvent', () => { expect(Array.from(replay.getContext().traceIds)).toEqual([]); expect(Array.from(replay.getContext().errorIds)).toEqual([]); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(Array.from(replay.getContext().errorIds)).toEqual([]); diff --git a/packages/replay-internal/test/integration/errorSampleRate.test.ts b/packages/replay-internal/test/integration/errorSampleRate.test.ts index 4f83f150548f..b3b605f28c12 100644 --- a/packages/replay-internal/test/integration/errorSampleRate.test.ts +++ b/packages/replay-internal/test/integration/errorSampleRate.test.ts @@ -1,4 +1,5 @@ import { captureException, getClient } from '@sentry/core'; +import { vi } from 'vitest'; import { BUFFER_CHECKOUT_TIME, @@ -23,12 +24,7 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -async function waitForBufferFlush() { - await new Promise(process.nextTick); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } @@ -72,13 +68,13 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -114,10 +110,10 @@ describe('Integration | errorSampleRate', () => { replayEventPayload: expect.objectContaining({ replay_type: 'buffer', }), - recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 40, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will get captured domHandler({ @@ -131,11 +127,11 @@ describe('Integration | errorSampleRate', () => { recordingData: JSON.stringify([ { type: 5, - timestamp: BASE_TIMESTAMP + 10000 + 80, + timestamp: BASE_TIMESTAMP + 10000, data: { tag: 'breadcrumb', payload: { - timestamp: (BASE_TIMESTAMP + 10000 + 80) / 1000, + timestamp: (BASE_TIMESTAMP + 10000) / 1000, type: 'default', category: 'ui.click', message: '', @@ -160,13 +156,13 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); replay.sendBufferedReplayOrFlush({ continueRecording: false }); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -194,7 +190,7 @@ describe('Integration | errorSampleRate', () => { ]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will not get captured domHandler({ name: 'click', @@ -245,14 +241,14 @@ describe('Integration | errorSampleRate', () => { name: 'click', event: new Event('click'), }); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); replay.sendBufferedReplayOrFlush({ continueRecording: true }); replay.sendBufferedReplayOrFlush({ continueRecording: true }); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -280,7 +276,7 @@ describe('Integration | errorSampleRate', () => { ]), }); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); // Check that click will not get captured domHandler({ name: 'click', @@ -309,7 +305,7 @@ describe('Integration | errorSampleRate', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); @@ -323,11 +319,11 @@ describe('Integration | errorSampleRate', () => { }, }); - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); @@ -342,13 +338,13 @@ describe('Integration | errorSampleRate', () => { }); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 100); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 100); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -357,7 +353,7 @@ describe('Integration | errorSampleRate', () => { }); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -374,14 +370,14 @@ describe('Integration | errorSampleRate', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -397,7 +393,7 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); @@ -408,7 +404,7 @@ describe('Integration | errorSampleRate', () => { // Fire a new event every 4 seconds, 4 times [...Array(4)].forEach(() => { mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4000); + vi.advanceTimersByTime(4000); }); // We are at time = +16seconds now (relative to BASE_TIMESTAMP) @@ -425,7 +421,7 @@ describe('Integration | errorSampleRate', () => { // Let's make sure it continues to work mockRecord._emitter(TEST_EVENT); await waitForFlush(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); }); @@ -440,7 +436,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -463,7 +459,7 @@ describe('Integration | errorSampleRate', () => { const sessionId = replay.getSessionId(); // Idle for given time - jest.advanceTimersByTime(waitTime + 1); + vi.advanceTimersByTime(waitTime + 1); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ @@ -472,7 +468,7 @@ describe('Integration | errorSampleRate', () => { }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // We stop recording after 15 minutes of inactivity in error mode @@ -499,7 +495,7 @@ describe('Integration | errorSampleRate', () => { expect(replay).not.toHaveLastSentReplay(); // Idle for given time - jest.advanceTimersByTime(waitTime + 1); + vi.advanceTimersByTime(waitTime + 1); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ @@ -508,7 +504,7 @@ describe('Integration | errorSampleRate', () => { }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // in production, this happens at a time interval, here we mock this @@ -536,7 +532,7 @@ describe('Integration | errorSampleRate', () => { // should still react to errors later on captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); expect(replay.session?.id).toBe(oldSessionId); @@ -560,7 +556,7 @@ describe('Integration | errorSampleRate', () => { expect(oldSessionId).toBeDefined(); // Idle for 15 minutes - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); const TEST_EVENT = getTestEventIncremental({ data: { name: 'lost event' }, @@ -569,7 +565,7 @@ describe('Integration | errorSampleRate', () => { mockRecord._emitter(TEST_EVENT); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); // We stop recording after SESSION_IDLE_EXPIRE_DURATION of inactivity in error mode @@ -582,7 +578,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); expect(replay.session?.id).toBe(oldSessionId); @@ -617,16 +613,12 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); - - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); captureException(new Error('testing')); - await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); - await new Promise(process.nextTick); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveSentReplay({ recordingData: JSON.stringify([ @@ -636,14 +628,10 @@ describe('Integration | errorSampleRate', () => { ]), replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, - // the exception happens roughly 10 seconds after BASE_TIMESTAMP - // (advance timers + waiting for flush after the checkout) and - // extra time is likely due to async of `addMemoryEntry()` - - timestamp: (BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + DEFAULT_FLUSH_MIN_DELAY + 40) / 1000, + timestamp: (BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + DEFAULT_FLUSH_MIN_DELAY) / 1000, error_ids: [expect.any(String)], trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), recordingPayloadHeader: { segment_id: 0 }, @@ -652,49 +640,49 @@ describe('Integration | errorSampleRate', () => { it('has correct timestamps when error occurs much later than initial pageload/checkout', async () => { const ELAPSED = BUFFER_CHECKOUT_TIME; - const TICK = 20; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); // add a mock performance event replay.performanceEntries.push(PerformanceEntryResource()); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // in production, this happens at a time interval // session started time should be updated to this current timestamp mockRecord.takeFullSnapshot(true); const optionsEvent = createOptionsEvent(replay); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + // await vi.advanceTimersToNextTimerAsync(); // This is still the timestamp from the full snapshot we took earlier - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + TICK); + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED); // Does not capture mouse click expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ // Make sure the old performance event is thrown out - replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + TICK) / 1000, + replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED) / 1000, }), recordingData: JSON.stringify([ { data: { isCheckout: true }, - timestamp: BASE_TIMESTAMP + ELAPSED + TICK, + timestamp: BASE_TIMESTAMP + ELAPSED, type: 2, }, optionsEvent, @@ -703,7 +691,7 @@ describe('Integration | errorSampleRate', () => { }); it('refreshes replay when user goes idle', async () => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -711,12 +699,10 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); - captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay(); @@ -725,14 +711,14 @@ describe('Integration | errorSampleRate', () => { // Now wait after session expires - should stop recording mockRecord.takeFullSnapshot.mockClear(); - (getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); + (getClient()!.getTransport()!.send as unknown as MockInstance).mockClear(); expect(replay).not.toHaveLastSentReplay(); const sessionId = replay.getSessionId(); // Go idle - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); @@ -749,7 +735,7 @@ describe('Integration | errorSampleRate', () => { it('refreshes replay when session exceeds max length after latest captured error', async () => { const sessionId = replay.session?.id; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -757,10 +743,9 @@ describe('Integration | errorSampleRate', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(replay).not.toHaveLastSentReplay(); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); - jest.advanceTimersByTime(2 * MAX_REPLAY_DURATION); + await vi.advanceTimersByTimeAsync(2 * MAX_REPLAY_DURATION); // in production, this happens at a time interval, here we mock this mockRecord.takeFullSnapshot(true); @@ -768,11 +753,11 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); // Flush due to exception - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); await waitForFlush(); expect(replay.session?.id).toBe(sessionId); - expect(replay).toHaveLastSentReplay({ + expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, }); @@ -785,7 +770,7 @@ describe('Integration | errorSampleRate', () => { data: { isCheckout: true, }, - timestamp: BASE_TIMESTAMP + 2 * MAX_REPLAY_DURATION + DEFAULT_FLUSH_MIN_DELAY + 40, + timestamp: BASE_TIMESTAMP + 2 * MAX_REPLAY_DURATION + DEFAULT_FLUSH_MIN_DELAY, type: 2, }, ]), @@ -793,14 +778,12 @@ describe('Integration | errorSampleRate', () => { // Now wait after session expires - should stop recording mockRecord.takeFullSnapshot.mockClear(); - (getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); + (getClient()!.getTransport()!.send as unknown as MockInstance).mockClear(); - jest.advanceTimersByTime(MAX_REPLAY_DURATION); - await new Promise(process.nextTick); + await advanceTimers(MAX_REPLAY_DURATION); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveLastSentReplay(); expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); @@ -812,7 +795,7 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); await new Promise(process.nextTick); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay(); }); @@ -821,14 +804,14 @@ describe('Integration | errorSampleRate', () => { const stepDuration = 10_000; const steps = 5_000; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); expect(replay).not.toHaveLastSentReplay(); let optionsEvent = createOptionsEvent(replay); for (let i = 1; i <= steps; i++) { - jest.advanceTimersByTime(stepDuration); + vi.advanceTimersByTime(stepDuration); optionsEvent = createOptionsEvent(replay); mockRecord._emitter({ data: { step: i }, timestamp: BASE_TIMESTAMP + stepDuration * i, type: 2 }, true); mockRecord._emitter({ data: { step: i }, timestamp: BASE_TIMESTAMP + stepDuration * i + 5, type: 3 }); @@ -842,7 +825,8 @@ describe('Integration | errorSampleRate', () => { // Now capture an error captureException(new Error('testing')); - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([ @@ -854,7 +838,7 @@ describe('Integration | errorSampleRate', () => { replay_start_timestamp: (BASE_TIMESTAMP + stepDuration * steps) / 1000, error_ids: [expect.any(String)], trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), recordingPayloadHeader: { segment_id: 0 }, @@ -890,7 +874,7 @@ describe('Integration | errorSampleRate', () => { // Waiting for max life should eventually refresh the session // We simulate a full checkout which would otherwise be done automatically for (let i = 0; i < MAX_REPLAY_DURATION / 60_000; i++) { - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); await new Promise(process.nextTick); mockRecord.takeFullSnapshot(true); } @@ -917,7 +901,7 @@ describe('Integration | errorSampleRate', () => { }); integration['_initialize'](); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); @@ -928,7 +912,7 @@ describe('Integration | errorSampleRate', () => { // Waiting for max life should eventually stop recording // We simulate a full checkout which would otherwise be done automatically for (let i = 0; i < MAX_REPLAY_DURATION / 60_000; i++) { - jest.advanceTimersByTime(60_000); + vi.advanceTimersByTime(60_000); await new Promise(process.nextTick); mockRecord.takeFullSnapshot(true); } @@ -962,9 +946,6 @@ describe('Integration | errorSampleRate', () => { integration['_initialize'](); const optionsEvent = createOptionsEvent(replay); - jest.runAllTimers(); - - await new Promise(process.nextTick); const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); @@ -973,7 +954,8 @@ describe('Integration | errorSampleRate', () => { captureException(new Error('testing')); // 2 ticks to send replay from an error - await waitForBufferFlush(); + await vi.advanceTimersToNextTimerAsync(); + await vi.advanceTimersToNextTimerAsync(); // Buffered events before error expect(replay).toHaveSentReplay({ @@ -986,13 +968,13 @@ describe('Integration | errorSampleRate', () => { }); // `startRecording()` after switching to session mode to continue recording - await waitForFlush(); + await vi.advanceTimersToNextTimerAsync(); // Latest checkout when we call `startRecording` again after uploading segment // after an error occurs (e.g. when we switch to session replay recording) expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 1 }, - recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + 40, type: 2 }]), + recordingData: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }]), }); }); }); diff --git a/packages/replay-internal/test/integration/eventProcessors.test.ts b/packages/replay-internal/test/integration/eventProcessors.test.ts index b683e1ac4279..58a0f488eb91 100644 --- a/packages/replay-internal/test/integration/eventProcessors.test.ts +++ b/packages/replay-internal/test/integration/eventProcessors.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient, getCurrentScope } from '@sentry/core'; import type { Event } from '@sentry/types'; @@ -15,7 +17,7 @@ describe('Integration | eventProcessors', () => { }); afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); }); it('handles event processors properly', async () => { @@ -29,17 +31,17 @@ describe('Integration | eventProcessors', () => { const client = getClient()!; - jest.runAllTimers(); - const mockTransportSend = jest.spyOn(client.getTransport()!, 'send'); + await vi.runAllTimersAsync(); + const mockTransportSend = vi.spyOn(client.getTransport()!, 'send'); mockTransportSend.mockReset(); - const handler1 = jest.fn((event: Event): Event | null => { + const handler1 = vi.fn((event: Event): Event | null => { event.timestamp = MUTATED_TIMESTAMP; return event; }); - const handler2 = jest.fn((): Event | null => { + const handler2 = vi.fn((): Event | null => { return null; }); @@ -48,8 +50,7 @@ describe('Integration | eventProcessors', () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - jest.runAllTimers(); - jest.advanceTimersByTime(1); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); @@ -59,8 +60,7 @@ describe('Integration | eventProcessors', () => { const TEST_EVENT2 = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT2); - jest.runAllTimers(); - jest.advanceTimersByTime(1); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); diff --git a/packages/replay-internal/test/integration/events.test.ts b/packages/replay-internal/test/integration/events.test.ts index 7a3d6e920a9e..c7670b70a0b6 100644 --- a/packages/replay-internal/test/integration/events.test.ts +++ b/packages/replay-internal/test/integration/events.test.ts @@ -14,19 +14,19 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } describe('Integration | events', () => { let replay: ReplayContainer; let mockRecord: RecordMock; - let mockTransportSend: jest.SpyInstance; + let mockTransportSend: MockInstance; const prevLocation = WINDOW.location; beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.runAllTimers(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.runAllTimers(); }); beforeEach(async () => { @@ -36,7 +36,7 @@ describe('Integration | events', () => { }, })); - mockTransportSend = jest.spyOn(getClient()!.getTransport()!, 'send'); + mockTransportSend = vi.spyOn(getClient()!.getTransport()!, 'send'); // Create a new session and clear mocks because a segment (from initial // checkout) will have already been uploaded by the time the tests run @@ -47,14 +47,14 @@ describe('Integration | events', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); Object.defineProperty(WINDOW, 'location', { value: prevLocation, writable: true, }); clearSession(replay); - jest.clearAllMocks(); + vi.clearAllMocks(); mockRecord.takeFullSnapshot.mockClear(); replay.stop(); }); @@ -86,7 +86,7 @@ describe('Integration | events', () => { expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + urls: ['http://localhost:3000/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough }), }); }); @@ -129,7 +129,7 @@ describe('Integration | events', () => { expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ replay_start_timestamp: (BASE_TIMESTAMP - 10000) / 1000, - urls: ['http://localhost/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough + urls: ['http://localhost:3000/'], // this doesn't truly test if we are capturing the right URL as we don't change URLs, but good enough }), }); }); @@ -160,7 +160,7 @@ describe('Integration | events', () => { addEvent(replay, TEST_EVENT); // This event will trigger a flush WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); diff --git a/packages/replay-internal/test/integration/flush.test.ts b/packages/replay-internal/test/integration/flush.test.ts index ee4cf456fbf9..bb00da732df3 100644 --- a/packages/replay-internal/test/integration/flush.test.ts +++ b/packages/replay-internal/test/integration/flush.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryUtils from '@sentry/utils'; @@ -5,7 +8,6 @@ import { DEFAULT_FLUSH_MIN_DELAY, MAX_REPLAY_DURATION, WINDOW } from '../../src/ import type { ReplayContainer } from '../../src/replay'; import { clearSession } from '../../src/session/clearSession'; import type { EventBuffer } from '../../src/types'; -import * as AddMemoryEntry from '../../src/util/addMemoryEntry'; import { createPerformanceEntries } from '../../src/util/createPerformanceEntries'; import { createPerformanceSpans } from '../../src/util/createPerformanceSpans'; import * as SendReplay from '../../src/util/sendReplay'; @@ -16,17 +18,11 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockSendReplay = jest.MockedFunction; -type MockAddPerformanceEntries = jest.MockedFunction; -type MockAddMemoryEntry = jest.SpyInstance; -type MockEventBufferFinish = jest.MockedFunction; -type MockFlush = jest.MockedFunction; -type MockRunFlush = jest.MockedFunction; +type MockSendReplay = MockedFunction; +type MockAddPerformanceEntries = MockedFunction; +type MockEventBufferFinish = MockedFunction; +type MockFlush = MockedFunction; +type MockRunFlush = MockedFunction; const prevLocation = WINDOW.location; const prevBrowserPerformanceTimeOrigin = SentryUtils.browserPerformanceTimeOrigin; @@ -41,48 +37,41 @@ describe('Integration | flush', () => { let mockFlush: MockFlush; let mockRunFlush: MockRunFlush; let mockEventBufferFinish: MockEventBufferFinish; - let mockAddMemoryEntry: MockAddMemoryEntry; let mockAddPerformanceEntries: MockAddPerformanceEntries; beforeAll(async () => { - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); ({ replay } = await mockSdk()); - mockSendReplay = jest.spyOn(SendReplay, 'sendReplay'); + mockSendReplay = vi.spyOn(SendReplay, 'sendReplay'); mockSendReplay.mockImplementation( - jest.fn(async () => { + vi.fn(async () => { return; }), ); // @ts-expect-error private API - mockFlush = jest.spyOn(replay, '_flush'); + mockFlush = vi.spyOn(replay, '_flush'); // @ts-expect-error private API - mockRunFlush = jest.spyOn(replay, '_runFlush'); + mockRunFlush = vi.spyOn(replay, '_runFlush'); // @ts-expect-error private API - mockAddPerformanceEntries = jest.spyOn(replay, '_addPerformanceEntries'); + mockAddPerformanceEntries = vi.spyOn(replay, '_addPerformanceEntries'); mockAddPerformanceEntries.mockImplementation(async () => { return []; }); - - mockAddMemoryEntry = jest.spyOn(AddMemoryEntry, 'addMemoryEntry'); }); - beforeEach(() => { - jest.runAllTimers(); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockSendReplay.mockClear(); + beforeEach(async () => { + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); replay.eventBuffer?.destroy(); - mockAddPerformanceEntries.mockClear(); - mockFlush.mockClear(); - mockRunFlush.mockClear(); - mockAddMemoryEntry.mockClear(); + vi.clearAllMocks(); sessionStorage.clear(); clearSession(replay); @@ -90,7 +79,7 @@ describe('Integration | flush', () => { replay.setInitialState(); if (replay.eventBuffer) { - jest.spyOn(replay.eventBuffer, 'finish'); + vi.spyOn(replay.eventBuffer, 'finish'); } mockEventBufferFinish = replay.eventBuffer?.finish as MockEventBufferFinish; mockEventBufferFinish.mockClear(); @@ -102,9 +91,8 @@ describe('Integration | flush', () => { }); afterEach(async () => { - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); Object.defineProperty(WINDOW, 'location', { value: prevLocation, @@ -133,16 +121,12 @@ describe('Integration | flush', () => { expect(mockFlush).toHaveBeenCalledTimes(4); - jest.runAllTimers(); - await new Promise(process.nextTick); expect(mockRunFlush).toHaveBeenCalledTimes(1); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockRunFlush).toHaveBeenCalledTimes(2); - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.advanceTimersToNextTimerAsync(); expect(mockRunFlush).toHaveBeenCalledTimes(2); }); @@ -164,18 +148,18 @@ describe('Integration | flush', () => { name: 'click', event: new Event('click'), }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // flush #2 @ t=5s - due to click expect(mockFlush).toHaveBeenCalledTimes(2); - await advanceTimers(1000); + await vi.advanceTimersByTimeAsync(1000); // flush #3 @ t=6s - due to blur WINDOW.dispatchEvent(new Event('blur')); expect(mockFlush).toHaveBeenCalledTimes(3); // NOTE: Blur also adds a breadcrumb which calls `addUpdate`, meaning it will // flush after `flushMinDelay`, but this gets cancelled by the blur - await advanceTimers(8000); + await vi.advanceTimersByTimeAsync(8000); expect(mockFlush).toHaveBeenCalledTimes(3); // flush #4 @ t=14s - due to blur @@ -183,7 +167,7 @@ describe('Integration | flush', () => { expect(mockFlush).toHaveBeenCalledTimes(4); expect(mockRunFlush).toHaveBeenCalledTimes(1); - await advanceTimers(6000); + await vi.advanceTimersByTimeAsync(6000); // t=20s // addPerformanceEntries is finished, `flushLock` promise is resolved, calls // debouncedFlush, which will call `flush` in 1 second @@ -236,7 +220,7 @@ describe('Integration | flush', () => { ); // flush #5 @ t=25s - debounced flush calls `flush` // 20s + `flushMinDelay` which is 5 seconds - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(5); expect(mockRunFlush).toHaveBeenCalledTimes(2); @@ -251,7 +235,7 @@ describe('Integration | flush', () => { }); // Make sure there's no other calls - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockSendReplay).toHaveBeenCalledTimes(2); }); @@ -267,11 +251,11 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); // Make sure there's nothing queued up after - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); }); @@ -293,13 +277,13 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); // it should re-schedule the flush, so once the min. duration is reached it should automatically send it - await advanceTimers(100_000 - DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(100_000 - DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(20); expect(mockSendReplay).toHaveBeenCalledTimes(1); @@ -309,7 +293,7 @@ describe('Integration | flush', () => { it('does not flush if session is too long', async () => { replay.getOptions().maxReplayDuration = 100_000; - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); sessionStorage.clear(); clearSession(replay); @@ -322,7 +306,7 @@ describe('Integration | flush', () => { return true; }; - await advanceTimers(120_000); + await vi.advanceTimersByTimeAsync(120_000); // click happens first domHandler({ @@ -334,7 +318,7 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); @@ -351,7 +335,7 @@ describe('Integration | flush', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); // Clear the event buffer to simulate no checkout happened replay.eventBuffer!.clear(); @@ -363,7 +347,7 @@ describe('Integration | flush', () => { }); // no checkout! - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(1); @@ -385,6 +369,21 @@ describe('Integration | flush', () => { }, }, }, + { + type: 5, + timestamp: BASE_TIMESTAMP, + data: { + tag: 'breadcrumb', + payload: { + timestamp: BASE_TIMESTAMP / 1000, + type: 'default', + category: 'console', + data: { logger: 'replay' }, + level: 'info', + message: '[Replay] Creating new session', + }, + }, + }, { type: 5, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, @@ -408,14 +407,14 @@ describe('Integration | flush', () => { it('logs warning if adding event that is after maxReplayDuration', async () => { replay.getOptions()._experiments.traceInternals = true; - const spyLogger = jest.spyOn(SentryUtils.logger, 'info'); + const spyLogger = vi.spyOn(SentryUtils.logger, 'info'); sessionStorage.clear(); clearSession(replay); replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); replay.eventBuffer!.clear(); @@ -427,7 +426,7 @@ describe('Integration | flush', () => { mockRecord._emitter(TEST_EVENT); // no checkout! - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // No flush is scheduled is aborted because event is after maxReplayDuration expect(mockFlush).toHaveBeenCalledTimes(0); @@ -457,7 +456,7 @@ describe('Integration | flush', () => { replay['_initializeSessionForSampling'](); replay.setInitialState(); await new Promise(process.nextTick); - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); replay.eventBuffer!.clear(); @@ -473,7 +472,7 @@ describe('Integration | flush', () => { const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP + 100 }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(160_000); + await vi.advanceTimersByTimeAsync(160_000); expect(mockFlush).toHaveBeenCalledTimes(1); expect(mockSendReplay).toHaveBeenCalledTimes(0); diff --git a/packages/replay-internal/test/integration/getReplayId.test.ts b/packages/replay-internal/test/integration/getReplayId.test.ts index 1080186974fc..7f24e5b1cbb7 100644 --- a/packages/replay-internal/test/integration/getReplayId.test.ts +++ b/packages/replay-internal/test/integration/getReplayId.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | getReplayId', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('works', async () => { diff --git a/packages/replay-internal/test/integration/integrationSettings.test.ts b/packages/replay-internal/test/integration/integrationSettings.test.ts index 3d7c180faf2f..b0ec35fc8a05 100644 --- a/packages/replay-internal/test/integration/integrationSettings.test.ts +++ b/packages/replay-internal/test/integration/integrationSettings.test.ts @@ -1,8 +1,11 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { mockSdk } from '../index'; describe('Integration | integrationSettings', () => { beforeEach(() => { - jest.resetModules(); + vi.resetModules(); }); describe('blockAllMedia', () => { @@ -14,10 +17,10 @@ describe('Integration | integrationSettings', () => { }); describe('replaysSessionSampleRate', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { @@ -53,10 +56,10 @@ describe('Integration | integrationSettings', () => { }); describe('replaysOnErrorSampleRate', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { @@ -92,10 +95,10 @@ describe('Integration | integrationSettings', () => { }); describe('all sample rates', () => { - let mockConsole: jest.SpyInstance; + let mockConsole: MockInstance; beforeEach(() => { - mockConsole = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + mockConsole = vi.spyOn(console, 'warn').mockImplementation(vi.fn()); }); afterEach(() => { diff --git a/packages/replay-internal/test/integration/rateLimiting.test.ts b/packages/replay-internal/test/integration/rateLimiting.test.ts index 70cba8f35eff..01e52aa641ef 100644 --- a/packages/replay-internal/test/integration/rateLimiting.test.ts +++ b/packages/replay-internal/test/integration/rateLimiting.test.ts @@ -10,18 +10,18 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); + vi.advanceTimersByTime(time); await new Promise(process.nextTick); } -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = vi.MockedFunction; describe('Integration | rate-limiting behaviour', () => { let replay: ReplayContainer; let mockTransportSend: MockTransportSend; beforeEach(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); ({ replay } = await mockSdk({ autoStart: false, @@ -35,7 +35,7 @@ describe('Integration | rate-limiting behaviour', () => { afterEach(async () => { clearSession(replay); - jest.clearAllMocks(); + vi.clearAllMocks(); replay && replay.stop(); }); @@ -61,7 +61,7 @@ describe('Integration | rate-limiting behaviour', () => { } as TransportMakeRequestResponse, ], ])('handles %s responses by stopping the replay', async (_name, { statusCode, headers }) => { - const mockStop = jest.spyOn(replay, 'stop'); + const mockStop = vi.spyOn(replay, 'stop'); mockTransportSend.mockImplementationOnce(() => { return Promise.resolve({ statusCode, headers }); @@ -93,7 +93,7 @@ describe('Integration | rate-limiting behaviour', () => { } as TransportMakeRequestResponse, ], ])('handles %s responses by not stopping', async (_name, { statusCode, headers }) => { - const mockStop = jest.spyOn(replay, 'stop'); + const mockStop = vi.spyOn(replay, 'stop'); mockTransportSend.mockImplementationOnce(() => { return Promise.resolve({ statusCode, headers }); diff --git a/packages/replay-internal/test/integration/rrweb.test.ts b/packages/replay-internal/test/integration/rrweb.test.ts index 4423c45246ea..1b571b356244 100644 --- a/packages/replay-internal/test/integration/rrweb.test.ts +++ b/packages/replay-internal/test/integration/rrweb.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | rrweb', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('calls rrweb.record with custom options', async () => { @@ -16,19 +16,19 @@ describe('Integration | rrweb', () => { }, }); expect(mockRecord.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "blockSelector": ".sentry-block,[data-sentry-block],base[href=\\"/\\"],img,image,svg,video,object,picture,embed,map,audio,link[rel=\\"icon\\"],link[rel=\\"apple-touch-icon\\"]", + { + "blockSelector": ".sentry-block,[data-sentry-block],base[href="/"],img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]", "collectFonts": true, "emit": [Function], "errorHandler": [Function], - "ignoreSelector": ".sentry-test-ignore,.sentry-ignore,[data-sentry-ignore],input[type=\\"file\\"]", + "ignoreSelector": ".sentry-test-ignore,.sentry-ignore,[data-sentry-ignore],input[type="file"]", "inlineImages": false, "inlineStylesheet": true, "maskAllInputs": true, "maskAllText": true, "maskAttributeFn": [Function], "maskInputFn": undefined, - "maskInputOptions": Object { + "maskInputOptions": { "password": true, }, "maskTextFn": undefined, diff --git a/packages/replay-internal/test/integration/sampling.test.ts b/packages/replay-internal/test/integration/sampling.test.ts index b82bb9538b5e..433a010b76aa 100644 --- a/packages/replay-internal/test/integration/sampling.test.ts +++ b/packages/replay-internal/test/integration/sampling.test.ts @@ -5,7 +5,7 @@ useFakeTimers(); describe('Integration | sampling', () => { beforeEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('does nothing if not sampled', async () => { @@ -20,8 +20,8 @@ describe('Integration | sampling', () => { }); // @ts-expect-error private API - const spyAddListeners = jest.spyOn(replay, '_addListeners'); - jest.runAllTimers(); + const spyAddListeners = vi.spyOn(replay, '_addListeners'); + vi.runAllTimers(); expect(replay.session).toBe(undefined); expect(replay.eventBuffer).toBeNull(); @@ -55,12 +55,12 @@ describe('Integration | sampling', () => { }); // @ts-expect-error private API - const spyAddListeners = jest.spyOn(replay, '_addListeners'); + const spyAddListeners = vi.spyOn(replay, '_addListeners'); // @ts-expect-error protected integration._initialize(); - jest.runAllTimers(); + vi.runAllTimers(); expect(replay.session?.id).toBeDefined(); expect(replay.eventBuffer).toBeDefined(); @@ -70,9 +70,9 @@ describe('Integration | sampling', () => { expect(replay.getContext()).toEqual({ errorIds: new Set(), initialTimestamp: expect.any(Number), - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', traceIds: new Set(), - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], }); expect(replay.recordingMode).toBe('buffer'); diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index 58cdecaf1c65..8e99f72ff517 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import * as SentryCore from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -14,23 +17,18 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - -type MockTransportSend = jest.MockedFunction; +type MockTransportSend = MockedFunction; describe('Integration | sendReplayEvent', () => { let replay: ReplayContainer; let mockTransportSend: MockTransportSend; - let mockSendReplayRequest: jest.SpyInstance; + let mockSendReplayRequest: MockInstance; let domHandler: DomHandler; const { record: mockRecord } = mockRrweb(); beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); @@ -45,14 +43,14 @@ describe('Integration | sendReplayEvent', () => { }, })); - mockSendReplayRequest = jest.spyOn(SendReplayRequest, 'sendReplayRequest'); + mockSendReplayRequest = vi.spyOn(SendReplayRequest, 'sendReplayRequest'); - jest.runAllTimers(); + await vi.runAllTimersAsync(); mockTransportSend = SentryCore.getClient()?.getTransport()?.send as MockTransportSend; }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); mockTransportSend.mockClear(); @@ -66,9 +64,9 @@ describe('Integration | sendReplayEvent', () => { }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); clearSession(replay); }); @@ -87,7 +85,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); @@ -121,7 +119,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); domHandler({ name: 'click', @@ -143,7 +141,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); domHandler({ name: 'input', @@ -158,7 +156,7 @@ describe('Integration | sendReplayEvent', () => { mockRecord._emitter(TEST_EVENT); // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -177,7 +175,7 @@ describe('Integration | sendReplayEvent', () => { // Fire a new event every 4 seconds, 4 times for (let i = 0; i < 4; i++) { mockRecord._emitter(TEST_EVENT); - jest.advanceTimersByTime(4_000); + vi.advanceTimersByTime(4_000); } // We are at time = +16seconds now (relative to BASE_TIMESTAMP) @@ -191,7 +189,7 @@ describe('Integration | sendReplayEvent', () => { // There should also not be another attempt at an upload 5 seconds after the last replay event mockTransportSend.mockClear(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).not.toHaveLastSentReplay(); expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); @@ -202,7 +200,7 @@ describe('Integration | sendReplayEvent', () => { // Let's make sure it continues to work mockTransportSend.mockClear(); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([TEST_EVENT]) }); }); @@ -216,7 +214,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); const hiddenBreadcrumb = { @@ -254,13 +252,13 @@ describe('Integration | sendReplayEvent', () => { }); // Pretend 5 seconds have passed const ELAPSED = 5000; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); addEvent(replay, TEST_EVENT); document.dispatchEvent(new Event('visibilitychange')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); @@ -281,7 +279,7 @@ describe('Integration | sendReplayEvent', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); expect(replay).toHaveLastSentReplay({ recordingData: JSON.stringify([ @@ -310,26 +308,26 @@ describe('Integration | sendReplayEvent', () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + const mockConsole = vi.spyOn(console, 'error').mockImplementation(vi.fn()); // fail the first and second requests and pass the third one mockTransportSend.mockImplementationOnce(() => { throw new Error('Something bad happened'); }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); mockTransportSend.mockImplementationOnce(() => { throw new Error('Something bad happened'); }); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); // next tick should retry and succeed mockConsole.mockRestore(); - await advanceTimers(8000); - await advanceTimers(2000); + await vi.advanceTimersByTimeAsync(8000); + await vi.advanceTimersByTimeAsync(2000); expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ @@ -339,7 +337,7 @@ describe('Integration | sendReplayEvent', () => { // timestamp is set on first try, after 5s flush timestamp: (BASE_TIMESTAMP + 5000) / 1000, trace_ids: [], - urls: ['http://localhost/'], + urls: ['http://localhost:3000/'], }), recordingPayloadHeader: { segment_id: 0 }, recordingData: JSON.stringify([TEST_EVENT]), @@ -351,17 +349,17 @@ describe('Integration | sendReplayEvent', () => { expect(replay.session?.segmentId).toBe(1); // next tick should do nothing - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).not.toHaveLastSentReplay(); }); it('fails to upload data and hits retry max and stops', async () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); - const spyHandleException = jest.spyOn(SentryCore, 'captureException'); + const spyHandleException = vi.spyOn(SentryCore, 'captureException'); // Suppress console.errors - const mockConsole = jest.spyOn(console, 'error').mockImplementation(jest.fn()); + const mockConsole = vi.spyOn(console, 'error').mockImplementation(vi.fn()); expect(replay.session?.segmentId).toBe(0); @@ -371,24 +369,24 @@ describe('Integration | sendReplayEvent', () => { }); mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(mockSendReplayRequest).toHaveBeenCalledTimes(1); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockSendReplayRequest).toHaveBeenCalledTimes(2); - await advanceTimers(10000); + await vi.advanceTimersByTimeAsync(10000); expect(mockSendReplayRequest).toHaveBeenCalledTimes(3); - await advanceTimers(30000); + await vi.advanceTimersByTimeAsync(30000); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); mockConsole.mockReset(); // Make sure it doesn't retry again - jest.runAllTimers(); + await vi.runAllTimersAsync(); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); // Retries = 3 (total tries = 4 including initial attempt) @@ -407,7 +405,7 @@ describe('Integration | sendReplayEvent', () => { // Events are ignored now, because we stopped mockRecord._emitter(TEST_EVENT); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(mockSendReplayRequest).toHaveBeenCalledTimes(4); }); diff --git a/packages/replay-internal/test/integration/session.test.ts b/packages/replay-internal/test/integration/session.test.ts index 0485fa78dd95..fb1327296ad2 100644 --- a/packages/replay-internal/test/integration/session.test.ts +++ b/packages/replay-internal/test/integration/session.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient } from '@sentry/core'; import type { Transport } from '@sentry/types'; @@ -24,11 +26,6 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -async function advanceTimers(time: number) { - jest.advanceTimersByTime(time); - await new Promise(process.nextTick); -} - const prevLocation = WINDOW.location; describe('Integration | session', () => { @@ -43,16 +40,16 @@ describe('Integration | session', () => { }, })); - const mockTransport = getClient()?.getTransport()?.send as jest.MockedFunction; + const mockTransport = getClient()?.getTransport()?.send as vi.MockedFunction; mockTransport?.mockClear(); + await vi.runAllTimersAsync(); }); afterEach(async () => { replay.stop(); - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); Object.defineProperty(WINDOW, 'location', { value: prevLocation, @@ -72,7 +69,7 @@ describe('Integration | session', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); document.dispatchEvent(new Event('visibilitychange')); @@ -83,7 +80,7 @@ describe('Integration | session', () => { it('does not create a new session when document becomes focused after [SESSION_IDLE_EXPIRE_DURATION]ms', () => { const initialSession = { ...replay.session } as Session; - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); WINDOW.dispatchEvent(new Event('focus')); @@ -105,7 +102,7 @@ describe('Integration | session', () => { expect(replay).toHaveSameSession(initialSession); // User comes back before `SESSION_IDLE_EXPIRE_DURATION` elapses - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 1); + vi.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 1); Object.defineProperty(document, 'visibilityState', { configurable: true, get: function () { @@ -126,7 +123,7 @@ describe('Integration | session', () => { expect(initialSession?.id).toBeDefined(); expect(replay.getContext()).toEqual( expect.objectContaining({ - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', initialTimestamp: BASE_TIMESTAMP, }), ); @@ -137,7 +134,7 @@ describe('Integration | session', () => { }); const ELAPSED = SESSION_IDLE_EXPIRE_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // Session has become in an idle state // @@ -203,10 +200,10 @@ describe('Integration | session', () => { // Replay does not send immediately because checkout was due to expired session expect(replay).not.toHaveLastSentReplay(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); - const newTimestamp = BASE_TIMESTAMP + ELAPSED + 20; + const newTimestamp = BASE_TIMESTAMP + ELAPSED; expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -236,7 +233,7 @@ describe('Integration | session', () => { expect(initialSession?.id).toBeDefined(); expect(replay.getContext()).toEqual( expect.objectContaining({ - initialUrl: 'http://localhost/', + initialUrl: 'http://localhost:3000/', initialTimestamp: BASE_TIMESTAMP, }), ); @@ -247,7 +244,7 @@ describe('Integration | session', () => { }); const ELAPSED = SESSION_IDLE_PAUSE_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); // Session has become in an idle state // @@ -305,7 +302,7 @@ describe('Integration | session', () => { // Replay does not send immediately expect(replay).not.toHaveLastSentReplay(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay(); }); @@ -326,17 +323,15 @@ describe('Integration | session', () => { }); it('creates a new session if current session exceeds MAX_REPLAY_DURATION', async () => { - jest.clearAllMocks(); + vi.clearAllMocks(); const initialSession = { ...replay.session } as Session; expect(initialSession?.id).toBeDefined(); - expect(replay.getContext()).toEqual( - expect.objectContaining({ - initialUrl: 'http://localhost/', - initialTimestamp: BASE_TIMESTAMP, - }), - ); + expect(replay.getContext()).toMatchObject({ + initialUrl: 'http://localhost:3000/', + initialTimestamp: BASE_TIMESTAMP, + }); const url = 'http://dummy/'; Object.defineProperty(WINDOW, 'location', { @@ -345,7 +340,7 @@ describe('Integration | session', () => { // Advanced past MAX_REPLAY_DURATION const ELAPSED = MAX_REPLAY_DURATION + 1; - jest.advanceTimersByTime(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); // Update activity so as to not consider session to be idling replay['_updateUserActivity'](); replay['_updateSessionActivity'](); @@ -360,8 +355,7 @@ describe('Integration | session', () => { const optionsEvent = createOptionsEvent(replay); const timestampAtRefresh = BASE_TIMESTAMP + ELAPSED; - jest.runAllTimers(); - await new Promise(process.nextTick); + await vi.runAllTimersAsync(); expect(replay).not.toHaveSameSession(initialSession); expect(replay).not.toHaveLastSentReplay(); @@ -373,17 +367,15 @@ describe('Integration | session', () => { event: new Event('click'), }); - // 20 is for the process.nextTick - const newTimestamp = timestampAtRefresh + 20; + const newTimestamp = timestampAtRefresh; const NEW_TEST_EVENT = getTestEventIncremental({ data: { name: 'test' }, - timestamp: newTimestamp + DEFAULT_FLUSH_MIN_DELAY + 20, + timestamp: newTimestamp + DEFAULT_FLUSH_MIN_DELAY, }); mockRecord._emitter(NEW_TEST_EVENT); - jest.runAllTimers(); - await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersByTimeAsync(DEFAULT_FLUSH_MIN_DELAY); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -431,7 +423,7 @@ describe('Integration | session', () => { // Pretend 5 seconds have passed const ELAPSED = 5000; - await advanceTimers(ELAPSED); + await vi.advanceTimersByTimeAsync(ELAPSED); const TEST_EVENT = getTestEventCheckout({ timestamp: BASE_TIMESTAMP }); @@ -445,7 +437,7 @@ describe('Integration | session', () => { addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay.session?.segmentId).toBe(2); expect(replay).toHaveLastSentReplay({ diff --git a/packages/replay-internal/test/integration/shouldFilterRequest.test.ts b/packages/replay-internal/test/integration/shouldFilterRequest.test.ts index 888b30e2c25f..207a8182581f 100644 --- a/packages/replay-internal/test/integration/shouldFilterRequest.test.ts +++ b/packages/replay-internal/test/integration/shouldFilterRequest.test.ts @@ -3,7 +3,7 @@ import { mockSdk } from '../index'; describe('Integration | shouldFilterRequest', () => { beforeEach(() => { - jest.resetModules(); + vi.resetModules(); }); it('should not filter requests from non-Sentry ingest URLs', async () => { diff --git a/packages/replay-internal/test/integration/stop.test.ts b/packages/replay-internal/test/integration/stop.test.ts index 3afbe3eeef1a..b4d256f3f9f0 100644 --- a/packages/replay-internal/test/integration/stop.test.ts +++ b/packages/replay-internal/test/integration/stop.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance, MockedFunction } from 'vitest'; + import * as SentryBrowserUtils from '@sentry-internal/browser-utils'; import { WINDOW } from '../../src/constants'; @@ -13,7 +16,7 @@ import { useFakeTimers } from '../utils/use-fake-timers'; useFakeTimers(); -type MockRunFlush = jest.MockedFunction; +type MockRunFlush = MockedFunction; describe('Integration | stop', () => { let replay: ReplayContainer; @@ -22,31 +25,31 @@ describe('Integration | stop', () => { const { record: mockRecord } = mockRrweb(); - let mockAddDomInstrumentationHandler: jest.SpyInstance; + let mockAddDomInstrumentationHandler: MockInstance; let mockRunFlush: MockRunFlush; beforeAll(async () => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - mockAddDomInstrumentationHandler = jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler'); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + mockAddDomInstrumentationHandler = vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler'); ({ replay, integration } = await mockSdk()); // @ts-expect-error private API - mockRunFlush = jest.spyOn(replay, '_runFlush'); + mockRunFlush = vi.spyOn(replay, '_runFlush'); - jest.runAllTimers(); + vi.runAllTimers(); }); beforeEach(() => { - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); replay.eventBuffer?.destroy(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); afterEach(async () => { - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); sessionStorage.clear(); clearSession(replay); replay['_initializeSessionForSampling'](); @@ -71,8 +74,6 @@ describe('Integration | stop', () => { }, }); const ELAPSED = 5000; - // Not sure where the 20ms comes from tbh - const EXTRA_TICKS = 20; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); const previousSessionId = replay.session?.id; @@ -80,7 +81,7 @@ describe('Integration | stop', () => { await integration.stop(); // Pretend 5 seconds have passed - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); @@ -99,9 +100,9 @@ describe('Integration | stop', () => { // will be different session expect(replay.session?.id).not.toEqual(previousSessionId); - jest.advanceTimersByTime(ELAPSED); + vi.advanceTimersByTime(ELAPSED); - const timestamp = +new Date(BASE_TIMESTAMP + ELAPSED + ELAPSED + EXTRA_TICKS) / 1000; + const timestamp = +new Date(BASE_TIMESTAMP + ELAPSED + ELAPSED) / 1000; const hiddenBreadcrumb = { type: 5, @@ -118,7 +119,7 @@ describe('Integration | stop', () => { addEvent(replay, TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); - jest.runAllTimers(); + vi.runAllTimers(); await new Promise(process.nextTick); expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, @@ -126,7 +127,7 @@ describe('Integration | stop', () => { // This event happens when we call `replay.start` { data: { isCheckout: true }, - timestamp: BASE_TIMESTAMP + ELAPSED + EXTRA_TICKS, + timestamp: BASE_TIMESTAMP + ELAPSED, type: 2, }, optionsEvent, @@ -137,7 +138,7 @@ describe('Integration | stop', () => { // Session's last activity is last updated when we call `setup()` and *NOT* // when tab is blurred - expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED + 20); + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP + ELAPSED); }); it('does not buffer new events after being stopped', async function () { diff --git a/packages/replay-internal/test/mocks/mockRrweb.ts b/packages/replay-internal/test/mocks/mockRrweb.ts index 30c5557298fc..bdffef9b7c32 100644 --- a/packages/replay-internal/test/mocks/mockRrweb.ts +++ b/packages/replay-internal/test/mocks/mockRrweb.ts @@ -1,10 +1,40 @@ -import type { record as rrwebRecord } from '@sentry-internal/rrweb'; +import { vi } from 'vitest'; +import type { Mock, MockedFunction } from 'vitest'; + +import { record } from '@sentry-internal/rrweb'; import type { RecordingEvent, ReplayEventWithTime } from '../../src/types'; import { ReplayEventTypeFullSnapshot, ReplayEventTypeIncrementalSnapshot } from '../../src/types'; +vi.mock('@sentry-internal/rrweb', async () => { + const mockRecordFn: Mock & Partial = vi.fn(({ emit }) => { + mockRecordFn._emitter = emit; + + emit(createCheckoutPayload()); + return function stop() { + mockRecordFn._emitter = vi.fn(); + }; + }); + mockRecordFn.takeFullSnapshot = vi.fn((isCheckout: boolean) => { + if (!mockRecordFn._emitter) { + return; + } + + mockRecordFn._emitter(createCheckoutPayload(isCheckout), isCheckout); + }); + + const ActualRrweb = await vi.importActual('@sentry-internal/rrweb'); + + mockRecordFn.mirror = ActualRrweb.record.mirror; + + return { + ...ActualRrweb, + record: mockRecordFn, + }; +}); + type RecordAdditionalProperties = { - takeFullSnapshot: jest.Mock; + takeFullSnapshot: Mock; // Below are not mocked addCustomEvent: () => void; @@ -15,7 +45,7 @@ type RecordAdditionalProperties = { _emitter: (event: RecordingEvent, ...args: any[]) => void; }; -export type RecordMock = jest.MockedFunction & RecordAdditionalProperties; +export type RecordMock = MockedFunction & RecordAdditionalProperties; function createCheckoutPayload(isCheckout: boolean = true): ReplayEventWithTime { return { @@ -26,34 +56,7 @@ function createCheckoutPayload(isCheckout: boolean = true): ReplayEventWithTime } export function mockRrweb(): { record: RecordMock } { - const mockRecordFn: jest.Mock & Partial = jest.fn(({ emit }) => { - mockRecordFn._emitter = emit; - - emit(createCheckoutPayload()); - return function stop() { - mockRecordFn._emitter = jest.fn(); - }; - }); - mockRecordFn.takeFullSnapshot = jest.fn((isCheckout: boolean) => { - if (!mockRecordFn._emitter) { - return; - } - - mockRecordFn._emitter(createCheckoutPayload(isCheckout), isCheckout); - }); - - jest.mock('@sentry-internal/rrweb', () => { - const ActualRrweb = jest.requireActual('@sentry-internal/rrweb'); - - mockRecordFn.mirror = ActualRrweb.record.mirror; - - return { - ...ActualRrweb, - record: mockRecordFn, - }; - }); - return { - record: mockRecordFn as RecordMock, + record: record as RecordMock, }; } diff --git a/packages/replay-internal/test/mocks/mockSdk.ts b/packages/replay-internal/test/mocks/mockSdk.ts index f57ef8c51d71..d5c9a088b779 100644 --- a/packages/replay-internal/test/mocks/mockSdk.ts +++ b/packages/replay-internal/test/mocks/mockSdk.ts @@ -1,4 +1,5 @@ import type { Envelope, Transport, TransportMakeRequestResponse } from '@sentry/types'; +import { vi } from 'vitest'; import type { Replay as ReplayIntegration } from '../../src/integration'; import type { ReplayContainer } from '../../src/replay'; @@ -16,7 +17,7 @@ class MockTransport implements Transport { send: (request: Envelope) => PromiseLike; constructor() { - this.send = jest.fn(async () => { + this.send = vi.fn(async () => { return { statusCode: 200, }; diff --git a/packages/replay-internal/test/mocks/resetSdkMock.ts b/packages/replay-internal/test/mocks/resetSdkMock.ts index 47a1522a4c63..ff2006b0604e 100644 --- a/packages/replay-internal/test/mocks/resetSdkMock.ts +++ b/packages/replay-internal/test/mocks/resetSdkMock.ts @@ -1,4 +1,5 @@ import { resetInstrumentationHandlers } from '@sentry/utils'; +import { vi } from 'vitest'; import type { Replay as ReplayIntegration } from '../../src/integration'; import type { ReplayContainer } from '../../src/replay'; @@ -16,15 +17,15 @@ export async function resetSdkMock({ replayOptions, sentryOptions, autoStart }: }> { let domHandler: DomHandler; - jest.setSystemTime(new Date(BASE_TIMESTAMP)); - jest.clearAllMocks(); - jest.resetModules(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); + vi.clearAllMocks(); + vi.resetModules(); // Clear all handlers that have been registered resetInstrumentationHandlers(); const SentryBrowserUtils = await import('@sentry-internal/browser-utils'); - jest.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { + vi.spyOn(SentryBrowserUtils, 'addClickKeypressInstrumentationHandler').mockImplementation(handler => { domHandler = handler; }); const { mockRrweb } = await import('./mockRrweb'); @@ -37,9 +38,8 @@ export async function resetSdkMock({ replayOptions, sentryOptions, autoStart }: }); // XXX: This is needed to ensure `domHandler` is set - jest.runAllTimers(); - await new Promise(process.nextTick); - jest.setSystemTime(new Date(BASE_TIMESTAMP)); + await vi.runAllTimersAsync(); + vi.setSystemTime(new Date(BASE_TIMESTAMP)); return { // @ts-expect-error use before assign diff --git a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts index ae52d2076293..07ef281800c1 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleClick.test.ts @@ -4,12 +4,12 @@ import { BASE_TIMESTAMP } from '../..'; import { ClickDetector, ignoreElement } from '../../../src/coreHandlers/handleClick'; import type { ReplayContainer } from '../../../src/types'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | handleClick', () => { describe('ClickDetector', () => { beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); test('it captures a single click', async () => { @@ -17,7 +17,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -41,15 +41,15 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -61,13 +61,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); @@ -76,7 +76,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -114,15 +114,15 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -134,13 +134,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: BASE_TIMESTAMP / 1000, }); - jest.advanceTimersByTime(2_000); + vi.advanceTimersByTime(2_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); expect(mockAddBreadcrumbEvent).toHaveBeenLastCalledWith(replay, { @@ -152,13 +152,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: (BASE_TIMESTAMP + 1200) / 1000, }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(2); }); @@ -167,7 +167,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -207,11 +207,11 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(3); }); @@ -220,7 +220,7 @@ describe('Unit | coreHandlers | handleClick', () => { getCurrentRoute: () => 'test-route', } as ReplayContainer; - const mockAddBreadcrumbEvent = jest.fn(); + const mockAddBreadcrumbEvent = vi.fn(); const detector = new ClickDetector( replay, @@ -260,23 +260,23 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); describe('mutations', () => { let detector: ClickDetector; - let mockAddBreadcrumbEvent = jest.fn(); + let mockAddBreadcrumbEvent = vi.fn(); const replay = { getCurrentRoute: () => 'test-route', } as ReplayContainer; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); - mockAddBreadcrumbEvent = jest.fn(); + mockAddBreadcrumbEvent = vi.fn(); detector = new ClickDetector( replay, @@ -302,14 +302,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(500); + vi.advanceTimersByTime(500); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 0.5; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); @@ -326,14 +326,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 2; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -345,13 +345,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 2000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); @@ -367,14 +367,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); // Pretend a mutation happend detector['_lastMutation'] = BASE_TIMESTAMP / 1000 + 5; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -386,29 +386,29 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); }); describe('scroll', () => { let detector: ClickDetector; - let mockAddBreadcrumbEvent = jest.fn(); + let mockAddBreadcrumbEvent = vi.fn(); const replay = { getCurrentRoute: () => 'test-route', } as ReplayContainer; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); - mockAddBreadcrumbEvent = jest.fn(); + mockAddBreadcrumbEvent = vi.fn(); detector = new ClickDetector( replay, @@ -434,14 +434,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); // Pretend a mutation happend detector['_lastScroll'] = BASE_TIMESTAMP / 1000 + 0.15; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); }); @@ -458,14 +458,14 @@ describe('Unit | coreHandlers | handleClick', () => { expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(300); + vi.advanceTimersByTime(300); // Pretend a mutation happend detector['_lastScroll'] = BASE_TIMESTAMP / 1000 + 0.3; expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(0); - jest.advanceTimersByTime(3_000); + vi.advanceTimersByTime(3_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); expect(mockAddBreadcrumbEvent).toHaveBeenCalledWith(replay, { @@ -477,13 +477,13 @@ describe('Unit | coreHandlers | handleClick', () => { nodeId: 1, route: 'test-route', timeAfterClickMs: 3000, - url: 'http://localhost/', + url: 'http://localhost:3000/', }, message: undefined, timestamp: expect.any(Number), }); - jest.advanceTimersByTime(5_000); + vi.advanceTimersByTime(5_000); expect(mockAddBreadcrumbEvent).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts b/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts index b3522c0ceb6c..ef81feef3a8e 100644 --- a/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/handleNetworkBreadcrumbs.test.ts @@ -14,7 +14,7 @@ import type { EventBufferArray } from '../../../src/eventBuffer/EventBufferArray import type { ReplayContainer, ReplayNetworkOptions } from '../../../src/types'; import { setupReplayContainer } from '../../utils/setupReplayContainer'; -jest.useFakeTimers(); +vi.useFakeTimers(); async function waitForReplayEventBuffer() { // Need one Promise.resolve() per await in the util functions @@ -56,7 +56,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => { }; beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); options = { replay: setupReplayContainer(), @@ -67,7 +67,7 @@ describe('Unit | coreHandlers | handleNetworkBreadcrumbs', () => { networkResponseHeaders: ['content-type', 'accept', 'x-custom-header'], }; - jest.runAllTimers(); + vi.runAllTimers(); }); it('ignores breadcrumb without data', async () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts index 45483ebab5b4..1979556b3dc9 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/addBreadcrumbEvent.test.ts @@ -3,11 +3,11 @@ import { addBreadcrumbEvent } from '../../../../src/coreHandlers/util/addBreadcr import type { EventBufferArray } from '../../../../src/eventBuffer/EventBufferArray'; import { setupReplayContainer } from '../../../utils/setupReplayContainer'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | util | addBreadcrumbEvent', function () { beforeEach(function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); it('handles circular references', async () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts index 8f240c8ea7a7..4a330081507a 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/networkUtils.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { NETWORK_BODY_MAX_SIZE } from '../../../../src/constants'; import { buildNetworkRequestOrResponse, @@ -7,7 +9,7 @@ import { parseContentLengthHeader, } from '../../../../src/coreHandlers/util/networkUtils'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | coreHandlers | util | networkUtils', () => { describe('parseContentLengthHeader()', () => { diff --git a/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts b/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts index d39b473f317f..6c805e6d2238 100644 --- a/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts +++ b/packages/replay-internal/test/unit/coreHandlers/util/xhrUtils.test.ts @@ -8,12 +8,12 @@ describe('Unit | coreHandlers | util | xhrUtils', () => { }); it('works with a Document', () => { - const body = document.implementation.createHTMLDocument(); + const doc = document.implementation.createHTMLDocument(); const bodyEl = document.createElement('body'); bodyEl.innerHTML = '
abc
'; - body.body = bodyEl; + doc.body = bodyEl; - const actual = _parseXhrResponse(body, ''); + const actual = _parseXhrResponse(doc, ''); expect(actual).toEqual(['
abc
']); }); diff --git a/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts b/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts index 6b314a0d1b24..5854a4fbaa6e 100644 --- a/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts +++ b/packages/replay-internal/test/unit/eventBuffer/EventBufferCompressionWorker.test.ts @@ -120,7 +120,7 @@ describe('Unit | eventBuffer | EventBufferCompressionWorker', () => { await buffer.addEvent(TEST_EVENT); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); @@ -141,7 +141,7 @@ describe('Unit | eventBuffer | EventBufferCompressionWorker', () => { await buffer.addEvent({ data: { o: 2 }, timestamp: BASE_TIMESTAMP, type: 3 }); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(buffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); diff --git a/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts b/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts index 101d061037dc..819813776c5d 100644 --- a/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts +++ b/packages/replay-internal/test/unit/eventBuffer/EventBufferProxy.test.ts @@ -9,11 +9,11 @@ import { createEventBuffer } from './../../../src/eventBuffer'; const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); describe('Unit | eventBuffer | EventBufferProxy', () => { - let consoleErrorSpy: jest.SpyInstance; + let consoleErrorSpy: MockInstance; beforeEach(() => { // Avoid logging errors to console - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { diff --git a/packages/replay-internal/test/unit/session/createSession.test.ts b/packages/replay-internal/test/unit/session/createSession.test.ts index 7713e821e74c..f6e6850754a5 100644 --- a/packages/replay-internal/test/unit/session/createSession.test.ts +++ b/packages/replay-internal/test/unit/session/createSession.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import * as Sentry from '@sentry/core'; import type { Hub } from '@sentry/types'; @@ -5,23 +7,23 @@ import { WINDOW } from '../../../src/constants'; import { createSession } from '../../../src/session/createSession'; import { saveSession } from '../../../src/session/saveSession'; -jest.mock('./../../../src/session/saveSession'); +vi.mock('./../../../src/session/saveSession'); -jest.mock('@sentry/utils', () => { +vi.mock('@sentry/utils', async () => { return { - ...(jest.requireActual('@sentry/utils') as { string: unknown }), - uuid4: jest.fn(() => 'test_session_id'), + ...((await vi.importActual('@sentry/utils')) as { string: unknown }), + uuid4: vi.fn(() => 'test_session_id'), }; }); -type CaptureEventMockType = jest.MockedFunction; +type CaptureEventMockType = vi.MockedFunction; describe('Unit | session | createSession', () => { - const captureEventMock: CaptureEventMockType = jest.fn(); + const captureEventMock: CaptureEventMockType = vi.fn(); beforeAll(() => { WINDOW.sessionStorage.clear(); - jest.spyOn(Sentry, 'getCurrentHub').mockImplementation(() => { + vi.spyOn(Sentry, 'getCurrentHub').mockImplementation(() => { return { captureEvent: captureEventMock, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts index bac9ee397984..51f4793c7768 100644 --- a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts +++ b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { MAX_REPLAY_DURATION, SESSION_IDLE_EXPIRE_DURATION, WINDOW } from '../../../src/constants'; import { makeSession } from '../../../src/session/Session'; import * as CreateSession from '../../../src/session/createSession'; @@ -6,10 +8,10 @@ import { loadOrCreateSession } from '../../../src/session/loadOrCreateSession'; import { saveSession } from '../../../src/session/saveSession'; import type { SessionOptions } from '../../../src/types'; -jest.mock('@sentry/utils', () => { +vi.mock('@sentry/utils', async () => { return { - ...(jest.requireActual('@sentry/utils') as { string: unknown }), - uuid4: jest.fn(() => 'test_session_uuid'), + ...((await vi.importActual('@sentry/utils')) as { string: unknown }), + uuid4: vi.fn(() => 'test_session_uuid'), }; }); @@ -42,15 +44,15 @@ function createMockSession(when: number = Date.now(), id = 'test_session_id') { describe('Unit | session | loadOrCreateSession', () => { beforeAll(() => { - jest.spyOn(CreateSession, 'createSession'); - jest.spyOn(FetchSession, 'fetchSession'); + vi.spyOn(CreateSession, 'createSession'); + vi.spyOn(FetchSession, 'fetchSession'); WINDOW.sessionStorage.clear(); }); afterEach(() => { WINDOW.sessionStorage.clear(); - (CreateSession.createSession as jest.MockedFunction).mockClear(); - (FetchSession.fetchSession as jest.MockedFunction).mockClear(); + (CreateSession.createSession as vi.MockedFunction).mockClear(); + (FetchSession.fetchSession as vi.MockedFunction).mockClear(); }); describe('stickySession: false', () => { diff --git a/packages/replay-internal/test/unit/util/addEvent.test.ts b/packages/replay-internal/test/unit/util/addEvent.test.ts index 8c1d9dea8175..748c2a87b684 100644 --- a/packages/replay-internal/test/unit/util/addEvent.test.ts +++ b/packages/replay-internal/test/unit/util/addEvent.test.ts @@ -1,5 +1,7 @@ import 'jsdom-worker'; +import { vi } from 'vitest'; + import { BASE_TIMESTAMP } from '../..'; import { MAX_REPLAY_DURATION, REPLAY_MAX_EVENT_BUFFER_SIZE, SESSION_IDLE_PAUSE_DURATION } from '../../../src/constants'; import type { EventBufferProxy } from '../../../src/eventBuffer/EventBufferProxy'; @@ -12,7 +14,7 @@ useFakeTimers(); describe('Unit | util | addEvent', () => { it('stops when encountering a compression error', async function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const replay = setupReplayContainer({ options: { @@ -20,10 +22,11 @@ describe('Unit | util | addEvent', () => { }, }); + await vi.runAllTimersAsync(); await (replay.eventBuffer as EventBufferProxy).ensureWorkerIsLoaded(); // @ts-expect-error Mock this private so it triggers an error - jest.spyOn(replay.eventBuffer._compression._worker, 'postMessage').mockImplementationOnce(() => { + vi.spyOn(replay.eventBuffer._compression._worker, 'postMessage').mockImplementationOnce(() => { return Promise.reject('test worker error'); }); @@ -33,7 +36,7 @@ describe('Unit | util | addEvent', () => { }); it('stops when exceeding buffer size limit', async function () { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); const replay = setupReplayContainer({ options: { @@ -41,6 +44,8 @@ describe('Unit | util | addEvent', () => { }, }); + await vi.runAllTimersAsync(); + const largeEvent = getTestEventIncremental({ data: { a: 'a'.repeat(REPLAY_MAX_EVENT_BUFFER_SIZE / 3) }, timestamp: BASE_TIMESTAMP, @@ -60,7 +65,7 @@ describe('Unit | util | addEvent', () => { describe('shouldAddEvent', () => { beforeEach(() => { - jest.setSystemTime(BASE_TIMESTAMP); + vi.setSystemTime(BASE_TIMESTAMP); }); it('returns true by default', () => { diff --git a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts index 636eb3aded9d..fd086eea6fa0 100644 --- a/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts +++ b/packages/replay-internal/test/unit/util/createPerformanceEntry.test.ts @@ -1,8 +1,11 @@ -jest.useFakeTimers().setSystemTime(new Date('2023-01-01')); +import { vi } from 'vitest'; -jest.mock('@sentry/utils', () => ({ - ...jest.requireActual('@sentry/utils'), - browserPerformanceTimeOrigin: Date.now(), +vi.useFakeTimers(); +vi.setSystemTime(new Date('2023-01-01')); + +vi.mock('@sentry/utils', async () => ({ + ...(await vi.importActual('@sentry/utils')), + browserPerformanceTimeOrigin: new Date('2023-01-01').getTime(), })); import { WINDOW } from '../../../src/constants'; @@ -12,7 +15,7 @@ import { PerformanceEntryNavigation } from '../../fixtures/performanceEntry/navi describe('Unit | util | createPerformanceEntries', () => { beforeEach(function () { if (!WINDOW.performance.getEntriesByType) { - WINDOW.performance.getEntriesByType = jest.fn((type: string) => { + WINDOW.performance.getEntriesByType = vi.fn((type: string) => { if (type === 'navigation') { return [PerformanceEntryNavigation()]; } @@ -22,8 +25,8 @@ describe('Unit | util | createPerformanceEntries', () => { }); afterAll(function () { - jest.clearAllMocks(); - jest.useRealTimers(); + vi.clearAllMocks(); + vi.useRealTimers(); }); it('ignores sdks own requests', function () { diff --git a/packages/replay-internal/test/unit/util/debounce.test.ts b/packages/replay-internal/test/unit/util/debounce.test.ts index d52f9d456b49..59eb9e2a84fa 100644 --- a/packages/replay-internal/test/unit/util/debounce.test.ts +++ b/packages/replay-internal/test/unit/util/debounce.test.ts @@ -1,77 +1,77 @@ import { debounce } from '../../../src/util/debounce'; describe('Unit | util | debounce', () => { - jest.useFakeTimers(); + vi.useFakeTimers(); it('delay the execution of the passed callback function by the passed minDelay', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).toHaveBeenCalled(); }); it('should invoke the callback at latest by maxWait, if the option is specified', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 150 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(98); + vi.advanceTimersByTime(98); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(49); + vi.advanceTimersByTime(49); // at this time, the callback shouldn't be invoked and with a new call, it should be devounced further. debouncedCallback(); expect(callback).not.toHaveBeenCalled(); // But because the maxWait is reached, the callback should nevertheless be invoked. - jest.advanceTimersByTime(10); + vi.advanceTimersByTime(10); expect(callback).toHaveBeenCalled(); }); it('should not invoke the callback as long as it is debounced and no maxWait option is specified', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(98); + vi.advanceTimersByTime(98); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); debouncedCallback(); - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); expect(callback).toHaveBeenCalled(); }); it('should invoke the callback as soon as callback.flush() is called', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 200 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(10); + vi.advanceTimersByTime(10); expect(callback).not.toHaveBeenCalled(); debouncedCallback.flush(); @@ -79,26 +79,26 @@ describe('Unit | util | debounce', () => { }); it('should not invoke the callback, if callback.cancel() is called', () => { - const callback = jest.fn(); + const callback = vi.fn(); const debouncedCallback = debounce(callback, 100, { maxWait: 200 }); debouncedCallback(); expect(callback).not.toHaveBeenCalled(); - jest.advanceTimersByTime(99); + vi.advanceTimersByTime(99); expect(callback).not.toHaveBeenCalled(); // If the callback is canceled, it should not be invoked after the minwait debouncedCallback.cancel(); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); expect(callback).not.toHaveBeenCalled(); // And it should also not be invoked after the maxWait - jest.advanceTimersByTime(500); + vi.advanceTimersByTime(500); expect(callback).not.toHaveBeenCalled(); }); it("should return the callback's return value when calling callback.flush()", () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); debouncedCallback(); @@ -108,7 +108,7 @@ describe('Unit | util | debounce', () => { }); it('should return the callbacks return value on subsequent calls of the debounced function', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); const returnValue1 = debouncedCallback(); @@ -116,7 +116,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(1); // calling the debounced function now should return the return value of the callback execution @@ -125,13 +125,13 @@ describe('Unit | util | debounce', () => { expect(callback).toHaveBeenCalledTimes(1); // and the callback should also be invoked again - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(2); }); it('should handle return values of consecutive invocations without maxWait', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 100); @@ -141,7 +141,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); expect(callback).toHaveBeenCalledTimes(1); // calling the debounced function now should return the return value of the callback execution @@ -149,13 +149,13 @@ describe('Unit | util | debounce', () => { expect(returnValue1).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // and the callback should also be invoked again - jest.advanceTimersByTime(200); + vi.advanceTimersByTime(200); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-2'); expect(callback).toHaveBeenCalledTimes(2); @@ -163,7 +163,7 @@ describe('Unit | util | debounce', () => { it('should handle return values of consecutive invocations with maxWait', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 150, { maxWait: 200 }); @@ -173,26 +173,26 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(149); + vi.advanceTimersByTime(149); const returnValue1 = debouncedCallback(); expect(returnValue1).toBe(undefined); expect(callback).not.toHaveBeenCalled(); // calling the debounced function now should return the return value of the callback execution // as it was executed because of maxWait - jest.advanceTimersByTime(51); + vi.advanceTimersByTime(51); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // at this point (100ms after the last debounce call), nothing should have happened - jest.advanceTimersByTime(100); + vi.advanceTimersByTime(100); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); // and the callback should now have been invoked again - jest.advanceTimersByTime(150); + vi.advanceTimersByTime(150); const returnValue4 = debouncedCallback(); expect(returnValue4).toBe('foo-2'); expect(callback).toHaveBeenCalledTimes(2); @@ -200,7 +200,7 @@ describe('Unit | util | debounce', () => { it('should handle return values of consecutive invocations after a cancellation', () => { let i = 0; - const callback = jest.fn().mockImplementation(() => { + const callback = vi.fn().mockImplementation(() => { return `foo-${++i}`; }); const debouncedCallback = debounce(callback, 150, { maxWait: 200 }); @@ -210,7 +210,7 @@ describe('Unit | util | debounce', () => { expect(callback).not.toHaveBeenCalled(); // now we expect the callback to have been invoked - jest.advanceTimersByTime(149); + vi.advanceTimersByTime(149); const returnValue1 = debouncedCallback(); expect(returnValue1).toBe(undefined); expect(callback).not.toHaveBeenCalled(); @@ -218,20 +218,20 @@ describe('Unit | util | debounce', () => { debouncedCallback.cancel(); // calling the debounced function now still return undefined because we cancelled the invocation - jest.advanceTimersByTime(51); + vi.advanceTimersByTime(51); const returnValue2 = debouncedCallback(); expect(returnValue2).toBe(undefined); expect(callback).not.toHaveBeenCalled(); // and the callback should also be invoked again - jest.advanceTimersByTime(150); + vi.advanceTimersByTime(150); const returnValue3 = debouncedCallback(); expect(returnValue3).toBe('foo-1'); expect(callback).toHaveBeenCalledTimes(1); }); it('should handle the return value of calling flush after cancelling', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100); debouncedCallback(); @@ -242,30 +242,30 @@ describe('Unit | util | debounce', () => { }); it('should handle equal wait and maxWait values and only invoke func once', () => { - const callback = jest.fn().mockReturnValue('foo'); + const callback = vi.fn().mockReturnValue('foo'); const debouncedCallback = debounce(callback, 100, { maxWait: 100 }); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); expect(callback).toHaveBeenCalledTimes(1); const retval = debouncedCallback(); expect(retval).toBe('foo'); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); debouncedCallback(); - jest.advanceTimersByTime(25); + vi.advanceTimersByTime(25); expect(callback).toHaveBeenCalledTimes(2); }); diff --git a/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts b/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts index e6d707ef879c..536ee7d0df21 100644 --- a/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts +++ b/packages/replay-internal/test/unit/util/getPrivacyOptions.test.ts @@ -1,11 +1,13 @@ +import { afterEach, beforeEach, describe, vi } from 'vitest'; + import { getPrivacyOptions } from '../../../src/util/getPrivacyOptions'; describe('Unit | util | getPrivacyOptions', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'warn').mockImplementation(() => {}); }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('has correct default options', () => { @@ -18,9 +20,9 @@ describe('Unit | util | getPrivacyOptions', () => { ignore: ['.custom-ignore'], }), ).toMatchInlineSnapshot(` - Object { - "blockSelector": ".custom-block,.sentry-block,[data-sentry-block],base[href=\\"/\\"]", - "ignoreSelector": ".custom-ignore,.sentry-ignore,[data-sentry-ignore],input[type=\\"file\\"]", + { + "blockSelector": ".custom-block,.sentry-block,[data-sentry-block],base[href="/"]", + "ignoreSelector": ".custom-ignore,.sentry-ignore,[data-sentry-ignore],input[type="file"]", "maskTextSelector": ".custom-mask,.sentry-mask,[data-sentry-mask]", "unblockSelector": ".custom-unblock", "unmaskTextSelector": ".custom-unmask", diff --git a/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts b/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts index 8e22c886a5eb..975762565e17 100644 --- a/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts +++ b/packages/replay-internal/test/unit/util/handleRecordingEmit.test.ts @@ -1,3 +1,6 @@ +import { vi } from 'vitest'; +import type { MockInstance } from 'vitest'; + import { EventType } from '@sentry-internal/rrweb'; import { BASE_TIMESTAMP } from '../..'; @@ -12,11 +15,11 @@ useFakeTimers(); let optionsEvent: ReplayOptionFrameEvent; describe('Unit | util | handleRecordingEmit', () => { - let addEventMock: jest.SpyInstance; + let addEventMock: MockInstance; beforeEach(function () { - jest.setSystemTime(BASE_TIMESTAMP); - addEventMock = jest.spyOn(SentryAddEvent, 'addEventSync').mockImplementation(() => { + vi.setSystemTime(BASE_TIMESTAMP); + addEventMock = vi.spyOn(SentryAddEvent, 'addEventSync').mockImplementation(() => { return true; }); }); @@ -45,14 +48,12 @@ describe('Unit | util | handleRecordingEmit', () => { }; handler(event); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(2); expect(addEventMock).toHaveBeenNthCalledWith(1, replay, event, true); expect(addEventMock).toHaveBeenLastCalledWith(replay, optionsEvent, false); handler(event); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(3); expect(addEventMock).toHaveBeenLastCalledWith(replay, event, false); @@ -78,7 +79,6 @@ describe('Unit | util | handleRecordingEmit', () => { }; handler(event, true); - await new Promise(process.nextTick); // Called twice, once for event and once for settings on checkout only expect(addEventMock).toBeCalledTimes(2); @@ -86,10 +86,9 @@ describe('Unit | util | handleRecordingEmit', () => { expect(addEventMock).toHaveBeenLastCalledWith(replay, optionsEvent, false); handler(event, true); - await new Promise(process.nextTick); expect(addEventMock).toBeCalledTimes(4); expect(addEventMock).toHaveBeenNthCalledWith(3, replay, event, true); - expect(addEventMock).toHaveBeenLastCalledWith(replay, { ...optionsEvent, timestamp: BASE_TIMESTAMP + 20 }, false); + expect(addEventMock).toHaveBeenLastCalledWith(replay, { ...optionsEvent, timestamp: BASE_TIMESTAMP }, false); }); }); diff --git a/packages/replay-internal/test/unit/util/isSampled.test.ts b/packages/replay-internal/test/unit/util/isSampled.test.ts index 97a824cb0e9d..6dfd163c45c7 100644 --- a/packages/replay-internal/test/unit/util/isSampled.test.ts +++ b/packages/replay-internal/test/unit/util/isSampled.test.ts @@ -14,7 +14,7 @@ const cases: [number, number, boolean][] = [ ]; describe('Unit | util | isSampled', () => { - const mockRandom = jest.spyOn(Math, 'random'); + const mockRandom = vi.spyOn(Math, 'random'); test.each(cases)( 'given sample rate of %p and RNG returns %p, result should be %p', diff --git a/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts b/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts index ac4ae503d10e..78c2c457a02d 100644 --- a/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts +++ b/packages/replay-internal/test/unit/util/prepareReplayEvent.test.ts @@ -1,3 +1,5 @@ +import { vi } from 'vitest'; + import { getClient, getCurrentScope, setCurrentClient } from '@sentry/core'; import type { ReplayEvent } from '@sentry/types'; @@ -11,7 +13,7 @@ describe('Unit | util | prepareReplayEvent', () => { setCurrentClient(client); client.init(); - jest.spyOn(client, 'getSdkMetadata').mockImplementation(() => { + vi.spyOn(client, 'getSdkMetadata').mockImplementation(() => { return { sdk: { name: 'sentry.javascript.testSdk', @@ -22,7 +24,7 @@ describe('Unit | util | prepareReplayEvent', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('works', async () => { diff --git a/packages/replay-internal/test/unit/util/throttle.test.ts b/packages/replay-internal/test/unit/util/throttle.test.ts index a242bde73398..797558dea904 100644 --- a/packages/replay-internal/test/unit/util/throttle.test.ts +++ b/packages/replay-internal/test/unit/util/throttle.test.ts @@ -1,14 +1,14 @@ import { BASE_TIMESTAMP } from '../..'; import { SKIPPED, THROTTLED, throttle } from '../../../src/util/throttle'; -jest.useFakeTimers(); +vi.useFakeTimers(); describe('Unit | util | throttle', () => { it('executes when not hitting the limit', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); - const callback = jest.fn(); + const callback = vi.fn(); const fn = throttle(callback, 100, 60); fn(); @@ -17,20 +17,20 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(59_000); + vi.advanceTimersByTime(59_000); fn(); fn(); expect(callback).toHaveBeenCalledTimes(5); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); expect(callback).toHaveBeenCalledTimes(6); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); @@ -39,8 +39,8 @@ describe('Unit | util | throttle', () => { it('stops executing when hitting the limit', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); - const callback = jest.fn(); + vi.setSystemTime(now); + const callback = vi.fn(); const fn = throttle(callback, 5, 60); fn(); @@ -49,14 +49,14 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(3); - jest.advanceTimersByTime(59_000); + vi.advanceTimersByTime(59_000); fn(); fn(); expect(callback).toHaveBeenCalledTimes(5); - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); fn(); @@ -66,7 +66,7 @@ describe('Unit | util | throttle', () => { expect(callback).toHaveBeenCalledTimes(5); // Now, the first three will "expire", so we can add three more - jest.advanceTimersByTime(1_000); + vi.advanceTimersByTime(1_000); fn(); fn(); @@ -78,9 +78,9 @@ describe('Unit | util | throttle', () => { it('has correct return value', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); - const callback = jest.fn(() => 'foo'); + const callback = vi.fn(() => 'foo'); const fn = throttle(callback, 5, 60); expect(fn()).toBe('foo'); @@ -93,7 +93,7 @@ describe('Unit | util | throttle', () => { expect(fn()).toBe(SKIPPED); // After 61s, should be reset - jest.advanceTimersByTime(61_000); + vi.advanceTimersByTime(61_000); expect(fn()).toBe('foo'); expect(fn()).toBe('foo'); @@ -107,10 +107,10 @@ describe('Unit | util | throttle', () => { it('passes args correctly', () => { const now = BASE_TIMESTAMP; - jest.setSystemTime(now); + vi.setSystemTime(now); const originalFunction = (a: number, b: number) => a + b; - const callback = jest.fn(originalFunction); + const callback = vi.fn(originalFunction); const fn = throttle(callback, 5, 60); expect(fn(1, 2)).toBe(3); diff --git a/packages/replay-internal/test/utils/TestClient.ts b/packages/replay-internal/test/utils/TestClient.ts index 26a14f2a9795..f95eb5309bcb 100644 --- a/packages/replay-internal/test/utils/TestClient.ts +++ b/packages/replay-internal/test/utils/TestClient.ts @@ -20,10 +20,8 @@ export class TestClient extends BaseClient { exception: { values: [ { - /* eslint-disable @typescript-eslint/no-unsafe-member-access */ type: exception.name, value: exception.message, - /* eslint-enable @typescript-eslint/no-unsafe-member-access */ }, ], }, diff --git a/packages/replay-internal/test/utils/use-fake-timers.ts b/packages/replay-internal/test/utils/use-fake-timers.ts index ef7c92dc8101..57fda295cb59 100644 --- a/packages/replay-internal/test/utils/use-fake-timers.ts +++ b/packages/replay-internal/test/utils/use-fake-timers.ts @@ -1,14 +1,5 @@ -export function useFakeTimers(): void { - const _setInterval = setInterval; - const _clearInterval = clearInterval; - jest.useFakeTimers(); - - let interval: any; - beforeAll(function () { - interval = _setInterval(() => jest.advanceTimersByTime(20), 20); - }); +import { vi } from 'vitest'; - afterAll(function () { - _clearInterval(interval); - }); +export function useFakeTimers(): void { + vi.useFakeTimers(); } diff --git a/packages/replay-internal/tsconfig.test.json b/packages/replay-internal/tsconfig.test.json index ad87caa06c48..1e96fa6b8945 100644 --- a/packages/replay-internal/tsconfig.test.json +++ b/packages/replay-internal/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], + "include": ["test/**/*.ts", "vitest.config.ts", "test.setup.ts"], "compilerOptions": { "types": ["node", "jest"], diff --git a/packages/replay-internal/vitest.config.ts b/packages/replay-internal/vitest.config.ts new file mode 100644 index 000000000000..9b4a2451c0c4 --- /dev/null +++ b/packages/replay-internal/vitest.config.ts @@ -0,0 +1,16 @@ +import type { UserConfig } from 'vitest'; +import { defineConfig } from 'vitest/config'; + +import baseConfig from '../../vite/vite.config'; + +export default defineConfig({ + ...baseConfig, + test: { + ...(baseConfig as UserConfig & { test: any }).test, + coverage: {}, + globals: true, + setupFiles: ['./test.setup.ts'], + reporters: ['default'], + environment: 'jsdom', + }, +}); diff --git a/yarn.lock b/yarn.lock index 17831308adfb..299357635fca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19648,6 +19648,16 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -19701,6 +19711,11 @@ jest-get-type@^29.4.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -19762,6 +19777,16 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-matcher-utils@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf"