18
18
import {
19
19
spy ,
20
20
stub ,
21
+ restore as sinonRestore ,
21
22
SinonSpy ,
22
23
SinonStub ,
23
24
useFakeTimers ,
@@ -26,12 +27,21 @@ import {
26
27
import { expect } from 'chai' ;
27
28
import { Api , setupApi , EntryType } from './api_service' ;
28
29
import * as iidService from './iid_service' ;
29
- import { setupOobResources } from './oob_resources_service' ;
30
+ import { setupOobResources , resetForUnitTests } from './oob_resources_service' ;
30
31
import { Trace } from '../resources/trace' ;
31
32
import '../../test/setup' ;
32
33
import { PerformanceController } from '../controllers/perf' ;
33
34
import { FirebaseApp } from '@firebase/app' ;
34
35
import { FirebaseInstallations } from '@firebase/installations-types' ;
36
+ import { WebVitalMetrics } from '../resources/web_vitals' ;
37
+ import {
38
+ CLSAttribution ,
39
+ CLSMetricWithAttribution ,
40
+ INPAttribution ,
41
+ INPMetricWithAttribution ,
42
+ LCPAttribution ,
43
+ LCPMetricWithAttribution
44
+ } from 'web-vitals/attribution' ;
35
45
36
46
describe ( 'Firebase Performance > oob_resources_service' , ( ) => {
37
47
const MOCK_ID = 'idasdfsffe' ;
@@ -82,23 +92,36 @@ describe('Firebase Performance > oob_resources_service', () => {
82
92
83
93
let getIidStub : SinonStub < [ ] , string | undefined > ;
84
94
let apiGetInstanceSpy : SinonSpy < [ ] , Api > ;
95
+ let eventListenerSpy : SinonSpy <
96
+ [
97
+ type : string ,
98
+ listener : EventListenerOrEventListenerObject ,
99
+ options ?: boolean | AddEventListenerOptions | undefined
100
+ ] ,
101
+ void
102
+ > ;
85
103
let getEntriesByTypeStub : SinonStub < [ EntryType ] , PerformanceEntry [ ] > ;
86
104
let setupObserverStub : SinonStub <
87
105
[ EntryType , ( entry : PerformanceEntry ) => void ] ,
88
106
void
89
107
> ;
90
- let createOobTraceStub : SinonStub <
108
+ let createOobTraceStub : SinonSpy <
91
109
[
92
110
PerformanceController ,
93
111
PerformanceNavigationTiming [ ] ,
94
112
PerformanceEntry [ ] ,
113
+ WebVitalMetrics ,
95
114
( number | undefined ) ?
96
115
] ,
97
116
void
98
117
> ;
99
118
let clock : SinonFakeTimers ;
119
+ let lcpSpy : SinonSpy < [ ( m : LCPMetricWithAttribution ) => void ] , void > ;
120
+ let inpSpy : SinonSpy < [ ( m : INPMetricWithAttribution ) => void ] , void > ;
121
+ let clsSpy : SinonSpy < [ ( m : CLSMetricWithAttribution ) => void ] , void > ;
100
122
101
- setupApi ( self ) ;
123
+ const mockWindow = { ...self } ;
124
+ setupApi ( mockWindow ) ;
102
125
103
126
const fakeFirebaseConfig = {
104
127
apiKey : 'api-key' ,
@@ -120,9 +143,22 @@ describe('Firebase Performance > oob_resources_service', () => {
120
143
fakeInstallations
121
144
) ;
122
145
146
+ function callEventListener ( name : string ) : void {
147
+ for ( let i = eventListenerSpy . callCount ; i > 0 ; i -- ) {
148
+ const [ eventName , eventFn ] = eventListenerSpy . getCall ( i - 1 ) . args ;
149
+ if ( eventName === name ) {
150
+ if ( typeof eventFn === 'function' ) {
151
+ eventFn ( new CustomEvent ( name ) ) ;
152
+ }
153
+ }
154
+ }
155
+ }
156
+
123
157
beforeEach ( ( ) => {
158
+ resetForUnitTests ( ) ;
124
159
getIidStub = stub ( iidService , 'getIid' ) ;
125
- apiGetInstanceSpy = spy ( Api , 'getInstance' ) ;
160
+ eventListenerSpy = spy ( mockWindow . document , 'addEventListener' ) ;
161
+
126
162
clock = useFakeTimers ( ) ;
127
163
getEntriesByTypeStub = stub ( Api . prototype , 'getEntriesByType' ) . callsFake (
128
164
entry => {
@@ -133,14 +169,24 @@ describe('Firebase Performance > oob_resources_service', () => {
133
169
}
134
170
) ;
135
171
setupObserverStub = stub ( Api . prototype , 'setupObserver' ) ;
136
- createOobTraceStub = stub ( Trace , 'createOobTrace' ) ;
172
+ createOobTraceStub = spy ( Trace , 'createOobTrace' ) ;
173
+ const api = Api . getInstance ( ) ;
174
+ lcpSpy = spy ( api , 'onLCP' ) ;
175
+ inpSpy = spy ( api , 'onINP' ) ;
176
+ clsSpy = spy ( api , 'onCLS' ) ;
177
+ apiGetInstanceSpy = spy ( Api , 'getInstance' ) ;
137
178
} ) ;
138
179
139
180
afterEach ( ( ) => {
140
181
clock . restore ( ) ;
182
+ sinonRestore ( ) ;
183
+ const api = Api . getInstance ( ) ;
184
+ //@ts -ignore Assignment to read-only property.
185
+ api . onFirstInputDelay = undefined ;
141
186
} ) ;
142
187
143
- describe ( 'setupOobResources' , ( ) => {
188
+ // eslint-disable-next-line no-restricted-properties
189
+ describe . only ( 'setupOobResources' , ( ) => {
144
190
it ( 'does not start if there is no iid' , ( ) => {
145
191
getIidStub . returns ( undefined ) ;
146
192
setupOobResources ( performanceController ) ;
@@ -158,18 +204,49 @@ describe('Firebase Performance > oob_resources_service', () => {
158
204
expect ( setupObserverStub ) . to . be . calledWith ( 'resource' ) ;
159
205
} ) ;
160
206
161
- it ( 'sets up page load trace collection ' , ( ) => {
207
+ it ( 'does not create page load trace before hidden ' , ( ) => {
162
208
getIidStub . returns ( MOCK_ID ) ;
163
209
setupOobResources ( performanceController ) ;
164
210
clock . tick ( 1 ) ;
165
211
166
212
expect ( apiGetInstanceSpy ) . to . be . called ;
213
+ expect ( createOobTraceStub ) . not . to . be . called ;
214
+ } ) ;
215
+
216
+ it ( 'creates page load trace after hidden' , ( ) => {
217
+ getIidStub . returns ( MOCK_ID ) ;
218
+ setupOobResources ( performanceController ) ;
219
+ clock . tick ( 1 ) ;
220
+
221
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
222
+ callEventListener ( 'visibilitychange' ) ;
223
+
167
224
expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'navigation' ) ;
168
225
expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'paint' ) ;
169
226
expect ( createOobTraceStub ) . to . be . calledWithExactly (
170
227
performanceController ,
171
228
[ NAVIGATION_PERFORMANCE_ENTRY ] ,
172
- [ PAINT_PERFORMANCE_ENTRY ]
229
+ [ PAINT_PERFORMANCE_ENTRY ] ,
230
+ { } ,
231
+ undefined
232
+ ) ;
233
+ } ) ;
234
+
235
+ it ( 'creates page load trace after pagehide' , ( ) => {
236
+ getIidStub . returns ( MOCK_ID ) ;
237
+ setupOobResources ( performanceController ) ;
238
+ clock . tick ( 1 ) ;
239
+
240
+ callEventListener ( 'pagehide' ) ;
241
+
242
+ expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'navigation' ) ;
243
+ expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'paint' ) ;
244
+ expect ( createOobTraceStub ) . to . be . calledWithExactly (
245
+ performanceController ,
246
+ [ NAVIGATION_PERFORMANCE_ENTRY ] ,
247
+ [ PAINT_PERFORMANCE_ENTRY ] ,
248
+ { } ,
249
+ undefined
173
250
) ;
174
251
} ) ;
175
252
@@ -181,13 +258,19 @@ describe('Firebase Performance > oob_resources_service', () => {
181
258
setupOobResources ( performanceController ) ;
182
259
clock . tick ( 1 ) ;
183
260
261
+ // Force the page load event to be sent
262
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
263
+ callEventListener ( 'visibilitychange' ) ;
264
+
184
265
expect ( api . onFirstInputDelay ) . to . be . called ;
185
266
expect ( createOobTraceStub ) . not . to . be . called ;
186
267
clock . tick ( 5000 ) ;
187
268
expect ( createOobTraceStub ) . to . be . calledWithExactly (
188
269
performanceController ,
189
270
[ NAVIGATION_PERFORMANCE_ENTRY ] ,
190
- [ PAINT_PERFORMANCE_ENTRY ]
271
+ [ PAINT_PERFORMANCE_ENTRY ] ,
272
+ { } ,
273
+ undefined
191
274
) ;
192
275
} ) ;
193
276
@@ -206,10 +289,15 @@ describe('Firebase Performance > oob_resources_service', () => {
206
289
clock . tick ( 1 ) ;
207
290
firstInputDelayCallback ( FIRST_INPUT_DELAY ) ;
208
291
292
+ // Force the page load event to be sent
293
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
294
+ callEventListener ( 'visibilitychange' ) ;
295
+
209
296
expect ( createOobTraceStub ) . to . be . calledWithExactly (
210
297
performanceController ,
211
298
[ NAVIGATION_PERFORMANCE_ENTRY ] ,
212
299
[ PAINT_PERFORMANCE_ENTRY ] ,
300
+ { } ,
213
301
FIRST_INPUT_DELAY
214
302
) ;
215
303
} ) ;
@@ -223,5 +311,123 @@ describe('Firebase Performance > oob_resources_service', () => {
223
311
expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'measure' ) ;
224
312
expect ( setupObserverStub ) . to . be . calledWith ( 'measure' ) ;
225
313
} ) ;
314
+
315
+ it ( 'sends LCP metrics with attribution' , ( ) => {
316
+ getIidStub . returns ( MOCK_ID ) ;
317
+ setupOobResources ( performanceController ) ;
318
+ clock . tick ( 1 ) ;
319
+
320
+ lcpSpy . getCall ( - 1 ) . args [ 0 ] ( {
321
+ value : 12.34 ,
322
+ attribution : {
323
+ element : 'some-element'
324
+ } as LCPAttribution
325
+ } as LCPMetricWithAttribution ) ;
326
+
327
+ // Force the page load event to be sent
328
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
329
+ callEventListener ( 'visibilitychange' ) ;
330
+
331
+ expect ( createOobTraceStub ) . to . be . calledWithExactly (
332
+ performanceController ,
333
+ [ NAVIGATION_PERFORMANCE_ENTRY ] ,
334
+ [ PAINT_PERFORMANCE_ENTRY ] ,
335
+ {
336
+ lcp : { value : 12.34 , elementAttribution : 'some-element' }
337
+ } ,
338
+ undefined
339
+ ) ;
340
+ } ) ;
341
+
342
+ it ( 'sends INP metrics with attribution' , ( ) => {
343
+ getIidStub . returns ( MOCK_ID ) ;
344
+ setupOobResources ( performanceController ) ;
345
+ clock . tick ( 1 ) ;
346
+
347
+ inpSpy . getCall ( - 1 ) . args [ 0 ] ( {
348
+ value : 0.198 ,
349
+ attribution : {
350
+ interactionTarget : 'another-element'
351
+ } as INPAttribution
352
+ } as INPMetricWithAttribution ) ;
353
+
354
+ // Force the page load event to be sent
355
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
356
+ callEventListener ( 'visibilitychange' ) ;
357
+
358
+ expect ( createOobTraceStub ) . to . be . calledWithExactly (
359
+ performanceController ,
360
+ [ NAVIGATION_PERFORMANCE_ENTRY ] ,
361
+ [ PAINT_PERFORMANCE_ENTRY ] ,
362
+ {
363
+ inp : { value : 0.198 , elementAttribution : 'another-element' }
364
+ } ,
365
+ undefined
366
+ ) ;
367
+ } ) ;
368
+
369
+ it ( 'sends CLS metrics with attribution' , ( ) => {
370
+ getIidStub . returns ( MOCK_ID ) ;
371
+ setupOobResources ( performanceController ) ;
372
+ clock . tick ( 1 ) ;
373
+
374
+ clsSpy . getCall ( - 1 ) . args [ 0 ] ( {
375
+ value : 0.3 ,
376
+ // eslint-disable-next-line
377
+ attribution : {
378
+ largestShiftTarget : 'large-shift-element'
379
+ } as CLSAttribution
380
+ } as CLSMetricWithAttribution ) ;
381
+
382
+ // Force the page load event to be sent
383
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
384
+ callEventListener ( 'visibilitychange' ) ;
385
+
386
+ expect ( createOobTraceStub ) . to . be . calledWithExactly (
387
+ performanceController ,
388
+ [ NAVIGATION_PERFORMANCE_ENTRY ] ,
389
+ [ PAINT_PERFORMANCE_ENTRY ] ,
390
+ {
391
+ cls : { value : 0.3 , elementAttribution : 'large-shift-element' }
392
+ } ,
393
+ undefined
394
+ ) ;
395
+ } ) ;
396
+
397
+ it ( 'sends all core web vitals metrics' , ( ) => {
398
+ getIidStub . returns ( MOCK_ID ) ;
399
+ setupOobResources ( performanceController ) ;
400
+ clock . tick ( 1 ) ;
401
+
402
+ lcpSpy . getCall ( - 1 ) . args [ 0 ] ( {
403
+ value : 5.91 ,
404
+ attribution : { element : 'an-element' } as LCPAttribution
405
+ } as LCPMetricWithAttribution ) ;
406
+ inpSpy . getCall ( - 1 ) . args [ 0 ] ( {
407
+ value : 0.1
408
+ } as INPMetricWithAttribution ) ;
409
+ clsSpy . getCall ( - 1 ) . args [ 0 ] ( {
410
+ value : 0.3 ,
411
+ attribution : {
412
+ largestShiftTarget : 'large-shift-element'
413
+ } as CLSAttribution
414
+ } as CLSMetricWithAttribution ) ;
415
+
416
+ // Force the page load event to be sent
417
+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
418
+ callEventListener ( 'visibilitychange' ) ;
419
+
420
+ expect ( createOobTraceStub ) . to . be . calledWithExactly (
421
+ performanceController ,
422
+ [ NAVIGATION_PERFORMANCE_ENTRY ] ,
423
+ [ PAINT_PERFORMANCE_ENTRY ] ,
424
+ {
425
+ lcp : { value : 5.91 , elementAttribution : 'an-element' } ,
426
+ inp : { value : 0.1 , elementAttribution : undefined } ,
427
+ cls : { value : 0.3 , elementAttribution : 'large-shift-element' }
428
+ } ,
429
+ undefined
430
+ ) ;
431
+ } ) ;
226
432
} ) ;
227
433
} ) ;
0 commit comments