1818import {
1919 spy ,
2020 stub ,
21+ restore as sinonRestore ,
2122 SinonSpy ,
2223 SinonStub ,
2324 useFakeTimers ,
@@ -26,12 +27,21 @@ import {
2627import { expect } from 'chai' ;
2728import { Api , setupApi , EntryType } from './api_service' ;
2829import * as iidService from './iid_service' ;
29- import { setupOobResources } from './oob_resources_service' ;
30+ import { setupOobResources , resetForUnitTests } from './oob_resources_service' ;
3031import { Trace } from '../resources/trace' ;
3132import '../../test/setup' ;
3233import { PerformanceController } from '../controllers/perf' ;
3334import { FirebaseApp } from '@firebase/app' ;
3435import { 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' ;
3545
3646describe ( 'Firebase Performance > oob_resources_service' , ( ) => {
3747 const MOCK_ID = 'idasdfsffe' ;
@@ -82,23 +92,36 @@ describe('Firebase Performance > oob_resources_service', () => {
8292
8393 let getIidStub : SinonStub < [ ] , string | undefined > ;
8494 let apiGetInstanceSpy : SinonSpy < [ ] , Api > ;
95+ let eventListenerSpy : SinonSpy <
96+ [
97+ type : string ,
98+ listener : EventListenerOrEventListenerObject ,
99+ options ?: boolean | AddEventListenerOptions | undefined
100+ ] ,
101+ void
102+ > ;
85103 let getEntriesByTypeStub : SinonStub < [ EntryType ] , PerformanceEntry [ ] > ;
86104 let setupObserverStub : SinonStub <
87105 [ EntryType , ( entry : PerformanceEntry ) => void ] ,
88106 void
89107 > ;
90- let createOobTraceStub : SinonStub <
108+ let createOobTraceStub : SinonSpy <
91109 [
92110 PerformanceController ,
93111 PerformanceNavigationTiming [ ] ,
94112 PerformanceEntry [ ] ,
113+ WebVitalMetrics ,
95114 ( number | undefined ) ?
96115 ] ,
97116 void
98117 > ;
99118 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 > ;
100122
101- setupApi ( self ) ;
123+ const mockWindow = { ...self } ;
124+ setupApi ( mockWindow ) ;
102125
103126 const fakeFirebaseConfig = {
104127 apiKey : 'api-key' ,
@@ -120,9 +143,22 @@ describe('Firebase Performance > oob_resources_service', () => {
120143 fakeInstallations
121144 ) ;
122145
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+
123157 beforeEach ( ( ) => {
158+ resetForUnitTests ( ) ;
124159 getIidStub = stub ( iidService , 'getIid' ) ;
125- apiGetInstanceSpy = spy ( Api , 'getInstance' ) ;
160+ eventListenerSpy = spy ( mockWindow . document , 'addEventListener' ) ;
161+
126162 clock = useFakeTimers ( ) ;
127163 getEntriesByTypeStub = stub ( Api . prototype , 'getEntriesByType' ) . callsFake (
128164 entry => {
@@ -133,14 +169,24 @@ describe('Firebase Performance > oob_resources_service', () => {
133169 }
134170 ) ;
135171 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' ) ;
137178 } ) ;
138179
139180 afterEach ( ( ) => {
140181 clock . restore ( ) ;
182+ sinonRestore ( ) ;
183+ const api = Api . getInstance ( ) ;
184+ //@ts -ignore Assignment to read-only property.
185+ api . onFirstInputDelay = undefined ;
141186 } ) ;
142187
143- describe ( 'setupOobResources' , ( ) => {
188+ // eslint-disable-next-line no-restricted-properties
189+ describe . only ( 'setupOobResources' , ( ) => {
144190 it ( 'does not start if there is no iid' , ( ) => {
145191 getIidStub . returns ( undefined ) ;
146192 setupOobResources ( performanceController ) ;
@@ -158,18 +204,49 @@ describe('Firebase Performance > oob_resources_service', () => {
158204 expect ( setupObserverStub ) . to . be . calledWith ( 'resource' ) ;
159205 } ) ;
160206
161- it ( 'sets up page load trace collection ' , ( ) => {
207+ it ( 'does not create page load trace before hidden ' , ( ) => {
162208 getIidStub . returns ( MOCK_ID ) ;
163209 setupOobResources ( performanceController ) ;
164210 clock . tick ( 1 ) ;
165211
166212 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+
167224 expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'navigation' ) ;
168225 expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'paint' ) ;
169226 expect ( createOobTraceStub ) . to . be . calledWithExactly (
170227 performanceController ,
171228 [ 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
173250 ) ;
174251 } ) ;
175252
@@ -181,13 +258,19 @@ describe('Firebase Performance > oob_resources_service', () => {
181258 setupOobResources ( performanceController ) ;
182259 clock . tick ( 1 ) ;
183260
261+ // Force the page load event to be sent
262+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
263+ callEventListener ( 'visibilitychange' ) ;
264+
184265 expect ( api . onFirstInputDelay ) . to . be . called ;
185266 expect ( createOobTraceStub ) . not . to . be . called ;
186267 clock . tick ( 5000 ) ;
187268 expect ( createOobTraceStub ) . to . be . calledWithExactly (
188269 performanceController ,
189270 [ NAVIGATION_PERFORMANCE_ENTRY ] ,
190- [ PAINT_PERFORMANCE_ENTRY ]
271+ [ PAINT_PERFORMANCE_ENTRY ] ,
272+ { } ,
273+ undefined
191274 ) ;
192275 } ) ;
193276
@@ -206,10 +289,15 @@ describe('Firebase Performance > oob_resources_service', () => {
206289 clock . tick ( 1 ) ;
207290 firstInputDelayCallback ( FIRST_INPUT_DELAY ) ;
208291
292+ // Force the page load event to be sent
293+ stub ( mockWindow . document , 'visibilityState' ) . value ( 'hidden' ) ;
294+ callEventListener ( 'visibilitychange' ) ;
295+
209296 expect ( createOobTraceStub ) . to . be . calledWithExactly (
210297 performanceController ,
211298 [ NAVIGATION_PERFORMANCE_ENTRY ] ,
212299 [ PAINT_PERFORMANCE_ENTRY ] ,
300+ { } ,
213301 FIRST_INPUT_DELAY
214302 ) ;
215303 } ) ;
@@ -223,5 +311,123 @@ describe('Firebase Performance > oob_resources_service', () => {
223311 expect ( getEntriesByTypeStub ) . to . be . calledWith ( 'measure' ) ;
224312 expect ( setupObserverStub ) . to . be . calledWith ( 'measure' ) ;
225313 } ) ;
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+ } ) ;
226432 } ) ;
227433} ) ;
0 commit comments