@@ -36,19 +36,25 @@ afterEach(() => {
3636 ( client . getTransport ( ) ?. send as MockTransport ) . mockClear ( ) ;
3737} ) ;
3838
39- type SentReplayExpected = {
40- envelopeHeader ?: {
41- event_id : string ;
42- sent_at : string ;
43- sdk : {
44- name : string ;
45- version ?: string ;
46- } ;
39+ type EnvelopeHeader = {
40+ event_id : string ;
41+ sent_at : string ;
42+ sdk : {
43+ name : string ;
44+ version ?: string ;
4745 } ;
48- replayEventHeader ?: { type : 'replay_event' } ;
49- replayEventPayload ?: Record < string , unknown > ;
50- recordingHeader ?: { type : 'replay_recording' ; length : number } ;
51- recordingPayloadHeader ?: Record < string , unknown > ;
46+ } ;
47+
48+ type ReplayEventHeader = { type : 'replay_event' } ;
49+ type ReplayEventPayload = Record < string , unknown > ;
50+ type RecordingHeader = { type : 'replay_recording' ; length : number } ;
51+ type RecordingPayloadHeader = Record < string , unknown > ;
52+ type SentReplayExpected = {
53+ envelopeHeader ?: EnvelopeHeader ;
54+ replayEventHeader ?: ReplayEventHeader ;
55+ replayEventPayload ?: ReplayEventPayload ;
56+ recordingHeader ?: RecordingHeader ;
57+ recordingPayloadHeader ?: RecordingPayloadHeader ;
5258 events ?: string | Uint8Array ;
5359} ;
5460
@@ -70,20 +76,27 @@ const toHaveSameSession = function (received: jest.Mocked<ReplayContainer>, expe
7076 } ;
7177} ;
7278
73- /**
74- * Checks the last call to `fetch` and ensures a replay was uploaded by
75- * checking the `fetch()` request's body.
76- */
77- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
78- const toHaveSentReplay = function (
79- _received : jest . Mocked < ReplayContainer > ,
79+ type Result = {
80+ passed : boolean ;
81+ key : string ;
82+ expectedVal : SentReplayExpected [ keyof SentReplayExpected ] ;
83+ actualVal : SentReplayExpected [ keyof SentReplayExpected ] ;
84+ } ;
85+ type Call = [
86+ EnvelopeHeader ,
87+ [
88+ [ ReplayEventHeader | undefined , ReplayEventPayload | undefined ] ,
89+ [ RecordingHeader | undefined , RecordingPayloadHeader | undefined ] ,
90+ ] ,
91+ ] ;
92+ type CheckCallForSentReplayResult = { pass : boolean ; call : Call | undefined ; results : Result [ ] } ;
93+
94+ function checkCallForSentReplay (
95+ call : Call | undefined ,
8096 expected ?: SentReplayExpected | { sample : SentReplayExpected ; inverse : boolean } ,
81- ) {
82- const { calls } = ( getCurrentHub ( ) . getClient ( ) ?. getTransport ( ) ?. send as MockTransport ) . mock ;
83- const lastCall = calls [ calls . length - 1 ] ?. [ 0 ] ;
84-
85- const envelopeHeader = lastCall ?. [ 0 ] ;
86- const envelopeItems = lastCall ?. [ 1 ] || [ [ ] , [ ] ] ;
97+ ) : CheckCallForSentReplayResult {
98+ const envelopeHeader = call ?. [ 0 ] ;
99+ const envelopeItems = call ?. [ 1 ] || [ [ ] , [ ] ] ;
87100 const [ [ replayEventHeader , replayEventPayload ] , [ recordingHeader , recordingPayload ] = [ ] ] = envelopeItems ;
88101
89102 // @ts -ignore recordingPayload is always a string in our tests
@@ -116,34 +129,98 @@ const toHaveSentReplay = function (
116129 . map ( key => {
117130 const actualVal = actualObj [ key as keyof SentReplayExpected ] ;
118131 const expectedVal = expectedObj [ key as keyof SentReplayExpected ] ;
119- const matches = ! expectedVal || this . equals ( actualVal , expectedVal ) ;
132+ const passed = ! expectedVal || this . equals ( actualVal , expectedVal ) ;
120133
121- return [ matches , key , expectedVal , actualVal ] ;
134+ return { passed , key, expectedVal, actualVal } ;
122135 } )
123- . filter ( ( [ passed ] ) => ! passed )
136+ . filter ( ( { passed } ) => ! passed )
124137 : [ ] ;
125138
126- const payloadPassed = Boolean ( lastCall && ( ! expected || results . length === 0 ) ) ;
139+ const pass = Boolean ( call && ( ! expected || results . length === 0 ) ) ;
140+
141+ return {
142+ pass,
143+ call,
144+ results,
145+ } ;
146+ }
147+
148+ /**
149+ * Checks all calls to `fetch` and ensures a replay was uploaded by
150+ * checking the `fetch()` request's body.
151+ */
152+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
153+ const toHaveSentReplay = function (
154+ _received : jest . Mocked < ReplayContainer > ,
155+ expected ?: SentReplayExpected | { sample : SentReplayExpected ; inverse : boolean } ,
156+ ) {
157+ const { calls } = ( getCurrentHub ( ) . getClient ( ) ?. getTransport ( ) ?. send as MockTransport ) . mock ;
158+
159+ let result : CheckCallForSentReplayResult ;
160+
161+ for ( const currentCall of calls ) {
162+ result = checkCallForSentReplay . call ( this , currentCall [ 0 ] , expected ) ;
163+ if ( result . pass ) {
164+ break ;
165+ }
166+ }
167+
168+ // @ts -ignore use before assigned
169+ const { results, call, pass } = result ;
127170
128171 const options = {
129172 isNot : this . isNot ,
130173 promise : this . promise ,
131174 } ;
132175
133- const allPass = payloadPassed ;
134-
135176 return {
136- pass : allPass ,
177+ pass,
137178 message : ( ) =>
138- ! lastCall
139- ? allPass
179+ ! call
180+ ? pass
140181 ? 'Expected Replay to not have been sent, but a request was attempted'
141182 : 'Expected Replay to have been sent, but a request was not attempted'
142183 : `${ this . utils . matcherHint ( 'toHaveSentReplay' , undefined , undefined , options ) } \n\n${ results
143184 . map (
144- ( [ , key , expected , actual ] ) =>
145- `Expected (key: ${ key } ): ${ payloadPassed ? 'not ' : '' } ${ this . utils . printExpected ( expected ) } \n` +
146- `Received (key: ${ key } ): ${ this . utils . printReceived ( actual ) } ` ,
185+ ( { key, expectedVal, actualVal } : Result ) =>
186+ `Expected (key: ${ key } ): ${ pass ? 'not ' : '' } ${ this . utils . printExpected ( expectedVal ) } \n` +
187+ `Received (key: ${ key } ): ${ this . utils . printReceived ( actualVal ) } ` ,
188+ )
189+ . join ( '\n' ) } `,
190+ } ;
191+ } ;
192+
193+ /**
194+ * Checks the last call to `fetch` and ensures a replay was uploaded by
195+ * checking the `fetch()` request's body.
196+ */
197+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
198+ const toHaveLastSentReplay = function (
199+ _received : jest . Mocked < ReplayContainer > ,
200+ expected ?: SentReplayExpected | { sample : SentReplayExpected ; inverse : boolean } ,
201+ ) {
202+ const { calls } = ( getCurrentHub ( ) . getClient ( ) ?. getTransport ( ) ?. send as MockTransport ) . mock ;
203+ const lastCall = calls [ calls . length - 1 ] ?. [ 0 ] ;
204+
205+ const { results, call, pass } = checkCallForSentReplay . call ( this , lastCall , expected ) ;
206+
207+ const options = {
208+ isNot : this . isNot ,
209+ promise : this . promise ,
210+ } ;
211+
212+ return {
213+ pass,
214+ message : ( ) =>
215+ ! call
216+ ? pass
217+ ? 'Expected Replay to not have been sent, but a request was attempted'
218+ : 'Expected Replay to have last been sent, but a request was not attempted'
219+ : `${ this . utils . matcherHint ( 'toHaveSentReplay' , undefined , undefined , options ) } \n\n${ results
220+ . map (
221+ ( { key, expectedVal, actualVal } : Result ) =>
222+ `Expected (key: ${ key } ): ${ pass ? 'not ' : '' } ${ this . utils . printExpected ( expectedVal ) } \n` +
223+ `Received (key: ${ key } ): ${ this . utils . printReceived ( actualVal ) } ` ,
147224 )
148225 . join ( '\n' ) } `,
149226 } ;
@@ -152,17 +229,20 @@ const toHaveSentReplay = function (
152229expect . extend ( {
153230 toHaveSameSession,
154231 toHaveSentReplay,
232+ toHaveLastSentReplay,
155233} ) ;
156234
157235declare global {
158236 // eslint-disable-next-line @typescript-eslint/no-namespace
159237 namespace jest {
160238 interface AsymmetricMatchers {
161239 toHaveSentReplay ( expected ?: SentReplayExpected ) : void ;
240+ toHaveLastSentReplay ( expected ?: SentReplayExpected ) : void ;
162241 toHaveSameSession ( expected : undefined | Session ) : void ;
163242 }
164243 interface Matchers < R > {
165244 toHaveSentReplay ( expected ?: SentReplayExpected ) : R ;
245+ toHaveLastSentReplay ( expected ?: SentReplayExpected ) : R ;
166246 toHaveSameSession ( expected : undefined | Session ) : R ;
167247 }
168248 }
0 commit comments