From dcb10f7401f3c19b23edf0860bba763583be2de2 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 7 Dec 2022 16:48:15 -0500 Subject: [PATCH 1/2] feat(replay): Add `toHaveLastSentReplay` jest matcher Really we renamed the previous `toHaveSentReplay` -> `toHaveLastSentReplay` and added `toHaveSentReplay` to match all calls to transport. --- packages/replay/jest.setup.ts | 143 +++++++++++++----- .../test/unit/index-errorSampleRate.test.ts | 53 +++---- .../replay/test/unit/index-noSticky.test.ts | 16 +- packages/replay/test/unit/index.test.ts | 42 ++--- packages/replay/test/unit/stop.test.ts | 6 +- 5 files changed, 162 insertions(+), 98 deletions(-) diff --git a/packages/replay/jest.setup.ts b/packages/replay/jest.setup.ts index 24e2e2088aa4..291e67e1a0ae 100644 --- a/packages/replay/jest.setup.ts +++ b/packages/replay/jest.setup.ts @@ -37,19 +37,25 @@ afterEach(() => { (client.getTransport()?.send as MockTransport).mockClear(); }); -type SentReplayExpected = { - envelopeHeader?: { - event_id: string; - sent_at: string; - sdk: { - name: string; - version?: string; - }; +type EnvelopeHeader = { + event_id: string; + sent_at: string; + sdk: { + name: string; + version?: string; }; - replayEventHeader?: { type: 'replay_event' }; - replayEventPayload?: Record; - recordingHeader?: { type: 'replay_recording'; length: number }; - recordingPayloadHeader?: Record; +} + +type ReplayEventHeader = { type: 'replay_event' } +type ReplayEventPayload = Record +type RecordingHeader = { type: 'replay_recording'; length: number } +type RecordingPayloadHeader = Record +type SentReplayExpected = { + envelopeHeader?: EnvelopeHeader; + replayEventHeader?: ReplayEventHeader; + replayEventPayload?: ReplayEventPayload + recordingHeader?: RecordingHeader; + recordingPayloadHeader?: RecordingPayloadHeader events?: string | Uint8Array; }; @@ -71,20 +77,13 @@ const toHaveSameSession = function (received: jest.Mocked, expe }; }; -/** - * Checks the last call to `fetch` and ensures a replay was uploaded by - * checking the `fetch()` request's body. - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const toHaveSentReplay = function ( - _received: jest.Mocked, - expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, -) { - const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; - const lastCall = calls[calls.length - 1]?.[0]; +type Result = {passed: boolean, key: string, expectedVal:SentReplayExpected[keyof SentReplayExpected], actualVal: SentReplayExpected[keyof SentReplayExpected]}; +type Call = [EnvelopeHeader, [[ReplayEventHeader|undefined, ReplayEventPayload|undefined], [RecordingHeader|undefined, RecordingPayloadHeader|undefined]]]; +type CheckCallForSentReplayResult = {pass: boolean, call: Call|undefined, results: Result[]} - const envelopeHeader = lastCall?.[0]; - const envelopeItems = lastCall?.[1] || [[], []]; +function checkCallForSentReplay(call: Call|undefined, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }): CheckCallForSentReplayResult { + const envelopeHeader = call?.[0]; + const envelopeItems = call?.[1] || [[], []]; const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; // @ts-ignore recordingPayload is always a string in our tests @@ -117,42 +116,110 @@ const toHaveSentReplay = function ( .map(key => { const actualVal = actualObj[key as keyof SentReplayExpected]; const expectedVal = expectedObj[key as keyof SentReplayExpected]; - const matches = !expectedVal || this.equals(actualVal, expectedVal); + const passed = !expectedVal || this.equals(actualVal, expectedVal); - return [matches, key, expectedVal, actualVal]; + return {passed, key, expectedVal, actualVal}; }) - .filter(([passed]) => !passed) + .filter(({passed}) => !passed) : []; - const payloadPassed = Boolean(lastCall && (!expected || results.length === 0)); + const pass = Boolean(call && (!expected || results.length === 0)); + + return { + pass, + call, + results, + }; + +}; + + +/** + * Checks all calls to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveSentReplay = function ( + _received: jest.Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; + + let result: CheckCallForSentReplayResult; + + for (const currentCall of calls) { + result = checkCallForSentReplay.call(this, currentCall[0], expected); + if (result.pass) { + break; + } + } + + // @ts-ignore use before assigned + const {results, call, pass} = result; const options = { isNot: this.isNot, promise: this.promise, }; - const allPass = payloadPassed; - return { - pass: allPass, + pass, message: () => - !lastCall - ? allPass + !call + ? pass ? 'Expected Replay to not have been sent, but a request was attempted' : 'Expected Replay to have been sent, but a request was not attempted' : `${this.utils.matcherHint('toHaveSentReplay', undefined, undefined, options)}\n\n${results .map( - ([, key, expected, actual]) => - `Expected (key: ${key}): ${payloadPassed ? 'not ' : ''}${this.utils.printExpected(expected)}\n` + - `Received (key: ${key}): ${this.utils.printReceived(actual)}`, + ({key, expectedVal, actualVal}: Result) => + `Expected (key: ${key}): ${pass ? 'not ' : ''}${this.utils.printExpected(expectedVal)}\n` + + `Received (key: ${key}): ${this.utils.printReceived(actualVal)}`, + ) + .join('\n')}`, + }; +}; + +/** + * Checks the last call to `fetch` and ensures a replay was uploaded by + * checking the `fetch()` request's body. + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const toHaveLastSentReplay = function ( + _received: jest.Mocked, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +) { + const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; + const lastCall = calls[calls.length - 1]?.[0]; + + const {results, call, pass} = checkCallForSentReplay.call(this, lastCall, expected); + + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + return { + pass, + message: () => + !call + ? pass + ? 'Expected Replay to not have been sent, but a request was attempted' + : '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) => + `Expected (key: ${key}): ${pass ? 'not ' : ''}${this.utils.printExpected(expectedVal)}\n` + + `Received (key: ${key}): ${this.utils.printReceived(actualVal)}`, ) .join('\n')}`, }; }; + expect.extend({ toHaveSameSession, toHaveSentReplay, + toHaveLastSentReplay, }); declare global { @@ -160,10 +227,12 @@ declare global { 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; } } diff --git a/packages/replay/test/unit/index-errorSampleRate.test.ts b/packages/replay/test/unit/index-errorSampleRate.test.ts index b5d28029bf2a..742e5e0c7747 100644 --- a/packages/replay/test/unit/index-errorSampleRate.test.ts +++ b/packages/replay/test/unit/index-errorSampleRate.test.ts @@ -45,7 +45,7 @@ describe('Replay (errorSampleRate)', () => { mockRecord._emitter(TEST_EVENT); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Does not capture mouse click domHandler({ @@ -53,7 +53,7 @@ describe('Replay (errorSampleRate)', () => { }); jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); jest.runAllTimers(); @@ -87,17 +87,12 @@ describe('Replay (errorSampleRate)', () => { ]), }); - mockTransportSend.mockClear(); - expect(replay).not.toHaveSentReplay(); - - jest.runAllTimers(); - await new Promise(process.nextTick); jest.runAllTimers(); await new Promise(process.nextTick); // New checkout when we call `startRecording` again after uploading segment // after an error occurs - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ { data: { isCheckout: true }, @@ -113,15 +108,15 @@ describe('Replay (errorSampleRate)', () => { }); jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ { type: 5, - timestamp: BASE_TIMESTAMP + 15000 + 60, + timestamp: BASE_TIMESTAMP + 15000 + 40, data: { tag: 'breadcrumb', payload: { - timestamp: (BASE_TIMESTAMP + 15000 + 60) / 1000, + timestamp: (BASE_TIMESTAMP + 15000 + 40) / 1000, type: 'default', category: 'ui.click', message: '', @@ -148,7 +143,7 @@ describe('Replay (errorSampleRate)', () => { jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not send a replay if user hides the tab and comes back within 60 seconds', async () => { @@ -163,7 +158,7 @@ describe('Replay (errorSampleRate)', () => { jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // User comes back before `VISIBILITY_CHANGE_TIMEOUT` elapses jest.advanceTimersByTime(VISIBILITY_CHANGE_TIMEOUT - 100); @@ -179,7 +174,7 @@ describe('Replay (errorSampleRate)', () => { await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not upload a replay event when document becomes hidden', async () => { @@ -203,7 +198,7 @@ describe('Replay (errorSampleRate)', () => { await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not upload a replay event if 5 seconds have elapsed since the last replay event occurred', async () => { @@ -218,7 +213,7 @@ describe('Replay (errorSampleRate)', () => { jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not upload a replay event if 15 seconds have elapsed since the last replay upload', async () => { @@ -234,18 +229,18 @@ describe('Replay (errorSampleRate)', () => { mockRecord._emitter(TEST_EVENT); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // There should also not be another attempt at an upload 5 seconds after the last replay event await advanceTimers(5000); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Let's make sure it continues to work mockRecord._emitter(TEST_EVENT); await advanceTimers(5000); jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not upload if user has been idle for more than 15 minutes and comes back to move their mouse', async () => { @@ -260,7 +255,7 @@ describe('Replay (errorSampleRate)', () => { type: 3, }; mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); jest.runAllTimers(); await new Promise(process.nextTick); @@ -272,7 +267,7 @@ describe('Replay (errorSampleRate)', () => { // replay the event on top. Or maybe replay the event on top of a refresh // snapshot. - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); }); @@ -281,7 +276,7 @@ describe('Replay (errorSampleRate)', () => { mockRecord._emitter(TEST_EVENT); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); jest.runAllTimers(); await new Promise(process.nextTick); @@ -293,7 +288,7 @@ describe('Replay (errorSampleRate)', () => { jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), replayEventPayload: expect.objectContaining({ replay_start_timestamp: BASE_TIMESTAMP / 1000, @@ -337,18 +332,18 @@ describe('Replay (errorSampleRate)', () => { const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); captureException(new Error('testing')); jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 }, TEST_EVENT]), }); mockTransportSend.mockClear(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); jest.runAllTimers(); await new Promise(process.nextTick); @@ -357,7 +352,7 @@ describe('Replay (errorSampleRate)', () => { // New checkout when we call `startRecording` again after uploading segment // after an error occurs - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ { data: { isCheckout: true }, @@ -380,7 +375,7 @@ describe('Replay (errorSampleRate)', () => { await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); jest.advanceTimersByTime(ELAPSED); @@ -401,7 +396,7 @@ describe('Replay (errorSampleRate)', () => { expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 20); // Does not capture mouse click - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ // Make sure the old performance event is thrown out replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + 20) / 1000, diff --git a/packages/replay/test/unit/index-noSticky.test.ts b/packages/replay/test/unit/index-noSticky.test.ts index e1c42d63870a..adc50832c7b9 100644 --- a/packages/replay/test/unit/index-noSticky.test.ts +++ b/packages/replay/test/unit/index-noSticky.test.ts @@ -127,7 +127,7 @@ describe('Replay (no sticky)', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); // Session's last activity is not updated because we do not consider // visibilitystate as user being active @@ -167,7 +167,7 @@ describe('Replay (no sticky)', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); // No user activity to trigger an update expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); @@ -190,14 +190,14 @@ describe('Replay (no sticky)', () => { mockRecord._emitter(TEST_EVENT); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), }); // There should also not be another attempt at an upload 5 seconds after the last replay event mockTransport.mockClear(); await advanceTimers(5000); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); expect(replay.session?.segmentId).toBe(1); @@ -208,7 +208,7 @@ describe('Replay (no sticky)', () => { mockTransport.mockClear(); mockRecord._emitter(TEST_EVENT); await advanceTimers(5000); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); }); it('creates a new session if user has been idle for more than 15 minutes and comes back to move their mouse', async () => { @@ -228,7 +228,7 @@ describe('Replay (no sticky)', () => { type: 3, }; mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); await new Promise(process.nextTick); @@ -244,7 +244,7 @@ describe('Replay (no sticky)', () => { expect(replay).not.toHaveSameSession(initialSession); // Replay does not send immediately because checkout was due to expired session - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Now do a click domHandler({ @@ -256,7 +256,7 @@ describe('Replay (no sticky)', () => { const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, { diff --git a/packages/replay/test/unit/index.test.ts b/packages/replay/test/unit/index.test.ts index dc38bf33c4d1..bc9a62b671e3 100644 --- a/packages/replay/test/unit/index.test.ts +++ b/packages/replay/test/unit/index.test.ts @@ -254,7 +254,7 @@ describe('Replay', () => { WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT, hiddenBreadcrumb]), }); // Session's last activity should not be updated @@ -282,7 +282,7 @@ describe('Replay', () => { await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); // Session's last activity is not updated because we do not consider // visibilitystate as user being active @@ -300,7 +300,7 @@ describe('Replay', () => { expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); expect(mockTransportSend).toHaveBeenCalledTimes(1); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); // No user activity to trigger an update expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); @@ -323,7 +323,7 @@ describe('Replay', () => { mockRecord._emitter(TEST_EVENT); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([...Array(5)].map(() => TEST_EVENT)), }); @@ -331,7 +331,7 @@ describe('Replay', () => { mockTransportSend.mockClear(); await advanceTimers(5000); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); expect(replay.session?.segmentId).toBe(1); @@ -342,7 +342,7 @@ describe('Replay', () => { mockTransportSend.mockClear(); mockRecord._emitter(TEST_EVENT); await advanceTimers(5000); - expect(replay).toHaveSentReplay({ events: JSON.stringify([TEST_EVENT]) }); + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); }); it('creates a new session if user has been idle for 15 minutes and comes back to click their mouse', async () => { @@ -374,7 +374,7 @@ describe('Replay', () => { type: 3, }; mockRecord._emitter(TEST_EVENT); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); await new Promise(process.nextTick); @@ -386,7 +386,7 @@ describe('Replay', () => { // snapshot. expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Should be a new session expect(replay).not.toHaveSameSession(initialSession); @@ -401,7 +401,7 @@ describe('Replay', () => { const newTimestamp = BASE_TIMESTAMP + FIFTEEN_MINUTES; const breadcrumbTimestamp = newTimestamp + 20; // I don't know where this 20ms comes from - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, events: JSON.stringify([ { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, @@ -478,7 +478,7 @@ describe('Replay', () => { await advanceTimers(5000); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Should be the same session because user has been idle and no events have caused a new session to be created expect(replay).toHaveSameSession(initialSession); @@ -513,7 +513,7 @@ describe('Replay', () => { jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, events: JSON.stringify([ { data: { isCheckout: true }, timestamp: newTimestamp, type: 2 }, @@ -554,7 +554,7 @@ describe('Replay', () => { const ELAPSED = 5000; await advanceTimers(ELAPSED); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ { type: 5, @@ -602,7 +602,7 @@ describe('Replay', () => { await advanceTimers(8000); await advanceTimers(2000); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ error_ids: [], replay_id: expect.any(String), @@ -623,7 +623,7 @@ describe('Replay', () => { // next tick should do nothing await advanceTimers(5000); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('fails to upload data and hits retry max and stops', async () => { @@ -694,7 +694,7 @@ describe('Replay', () => { replay.addEvent(TEST_EVENT); WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 0 }, }); expect(replay.session?.segmentId).toBe(1); @@ -704,7 +704,7 @@ describe('Replay', () => { jest.runAllTimers(); await new Promise(process.nextTick); expect(replay.session?.segmentId).toBe(2); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ recordingPayloadHeader: { segment_id: 1 }, }); }); @@ -719,7 +719,7 @@ describe('Replay', () => { document.dispatchEvent(new Event('visibilitychange')); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Pretend 5 seconds have passed const ELAPSED = 5000; @@ -735,7 +735,7 @@ describe('Replay', () => { WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + 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 @@ -804,7 +804,7 @@ describe('Replay', () => { document.dispatchEvent(new Event('visibilitychange')); await new Promise(process.nextTick); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Pretend 5 seconds have passed const ELAPSED = 5000; @@ -827,7 +827,7 @@ describe('Replay', () => { WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + 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 @@ -873,7 +873,7 @@ describe('Replay', () => { await new Promise(process.nextTick); expect(mockTransportSend).toHaveBeenCalledTimes(1); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ replayEventPayload: expect.objectContaining({ // Make sure the old performance event is thrown out replay_start_timestamp: BASE_TIMESTAMP / 1000, diff --git a/packages/replay/test/unit/stop.test.ts b/packages/replay/test/unit/stop.test.ts index 767149e724df..b770cb17ec7e 100644 --- a/packages/replay/test/unit/stop.test.ts +++ b/packages/replay/test/unit/stop.test.ts @@ -77,7 +77,7 @@ describe('Replay - stop', () => { WINDOW.dispatchEvent(new Event('blur')); await new Promise(process.nextTick); expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); // Session's last activity should not be updated expect(replay.session?.lastActivity).toEqual(BASE_TIMESTAMP); // eventBuffer is destroyed @@ -107,7 +107,7 @@ describe('Replay - stop', () => { WINDOW.dispatchEvent(new Event('blur')); jest.runAllTimers(); await new Promise(process.nextTick); - expect(replay).toHaveSentReplay({ + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([ // This event happens when we call `replay.start` { @@ -137,7 +137,7 @@ describe('Replay - stop', () => { await new Promise(process.nextTick); expect(replay.eventBuffer?.length).toBe(undefined); - expect(replay).not.toHaveSentReplay(); + expect(replay).not.toHaveLastSentReplay(); }); it('does not call core SDK `addInstrumentationHandler` after initial setup', async function () { From d85c0387665e67a016cd5d79d63d9c97c32ee8a2 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 8 Dec 2022 17:47:42 -0500 Subject: [PATCH 2/2] prettier --- packages/replay/jest.setup.ts | 55 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/packages/replay/jest.setup.ts b/packages/replay/jest.setup.ts index 291e67e1a0ae..be405171c2e2 100644 --- a/packages/replay/jest.setup.ts +++ b/packages/replay/jest.setup.ts @@ -44,18 +44,18 @@ type EnvelopeHeader = { name: string; version?: string; }; -} +}; -type ReplayEventHeader = { type: 'replay_event' } -type ReplayEventPayload = Record -type RecordingHeader = { type: 'replay_recording'; length: number } -type RecordingPayloadHeader = Record +type ReplayEventHeader = { type: 'replay_event' }; +type ReplayEventPayload = Record; +type RecordingHeader = { type: 'replay_recording'; length: number }; +type RecordingPayloadHeader = Record; type SentReplayExpected = { envelopeHeader?: EnvelopeHeader; replayEventHeader?: ReplayEventHeader; - replayEventPayload?: ReplayEventPayload + replayEventPayload?: ReplayEventPayload; recordingHeader?: RecordingHeader; - recordingPayloadHeader?: RecordingPayloadHeader + recordingPayloadHeader?: RecordingPayloadHeader; events?: string | Uint8Array; }; @@ -77,11 +77,25 @@ const toHaveSameSession = function (received: jest.Mocked, expe }; }; -type Result = {passed: boolean, key: string, expectedVal:SentReplayExpected[keyof SentReplayExpected], actualVal: SentReplayExpected[keyof SentReplayExpected]}; -type Call = [EnvelopeHeader, [[ReplayEventHeader|undefined, ReplayEventPayload|undefined], [RecordingHeader|undefined, RecordingPayloadHeader|undefined]]]; -type CheckCallForSentReplayResult = {pass: boolean, call: Call|undefined, results: Result[]} - -function checkCallForSentReplay(call: Call|undefined, expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }): CheckCallForSentReplayResult { +type Result = { + passed: boolean; + key: string; + expectedVal: SentReplayExpected[keyof SentReplayExpected]; + actualVal: SentReplayExpected[keyof SentReplayExpected]; +}; +type Call = [ + EnvelopeHeader, + [ + [ReplayEventHeader | undefined, ReplayEventPayload | undefined], + [RecordingHeader | undefined, RecordingPayloadHeader | undefined], + ], +]; +type CheckCallForSentReplayResult = { pass: boolean; call: Call | undefined; results: Result[] }; + +function checkCallForSentReplay( + call: Call | undefined, + expected?: SentReplayExpected | { sample: SentReplayExpected; inverse: boolean }, +): CheckCallForSentReplayResult { const envelopeHeader = call?.[0]; const envelopeItems = call?.[1] || [[], []]; const [[replayEventHeader, replayEventPayload], [recordingHeader, recordingPayload] = []] = envelopeItems; @@ -118,9 +132,9 @@ function checkCallForSentReplay(call: Call|undefined, expected?: SentReplayExpec const expectedVal = expectedObj[key as keyof SentReplayExpected]; const passed = !expectedVal || this.equals(actualVal, expectedVal); - return {passed, key, expectedVal, actualVal}; + return { passed, key, expectedVal, actualVal }; }) - .filter(({passed}) => !passed) + .filter(({ passed }) => !passed) : []; const pass = Boolean(call && (!expected || results.length === 0)); @@ -130,9 +144,7 @@ function checkCallForSentReplay(call: Call|undefined, expected?: SentReplayExpec call, results, }; - -}; - +} /** * Checks all calls to `fetch` and ensures a replay was uploaded by @@ -155,7 +167,7 @@ const toHaveSentReplay = function ( } // @ts-ignore use before assigned - const {results, call, pass} = result; + const { results, call, pass } = result; const options = { isNot: this.isNot, @@ -171,7 +183,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) => + ({ key, expectedVal, actualVal }: Result) => `Expected (key: ${key}): ${pass ? 'not ' : ''}${this.utils.printExpected(expectedVal)}\n` + `Received (key: ${key}): ${this.utils.printReceived(actualVal)}`, ) @@ -191,7 +203,7 @@ const toHaveLastSentReplay = function ( const { calls } = (getCurrentHub().getClient()?.getTransport()?.send as MockTransport).mock; const lastCall = calls[calls.length - 1]?.[0]; - const {results, call, pass} = checkCallForSentReplay.call(this, lastCall, expected); + const { results, call, pass } = checkCallForSentReplay.call(this, lastCall, expected); const options = { isNot: this.isNot, @@ -207,7 +219,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) => + ({ key, expectedVal, actualVal }: Result) => `Expected (key: ${key}): ${pass ? 'not ' : ''}${this.utils.printExpected(expectedVal)}\n` + `Received (key: ${key}): ${this.utils.printReceived(actualVal)}`, ) @@ -215,7 +227,6 @@ const toHaveLastSentReplay = function ( }; }; - expect.extend({ toHaveSameSession, toHaveSentReplay,