@@ -12,6 +12,7 @@ describe('trackMFEVitals', () => {
1212 let mutationCallbacks
1313 let performanceCallbacks
1414 let timings
15+ const clockBaseline = 11
1516
1617 beforeEach ( async ( ) => {
1718 jest . resetModules ( )
@@ -62,7 +63,9 @@ describe('trackMFEVitals', () => {
6263 isBrowserScope : true
6364 } ) )
6465
65- // Mock now() to return incrementing values
66+ // Mock now() to return incrementing values.
67+ // The first relative LCP tick lands on `clockBaseline + 1` because the collector
68+ // consumes one clock tick when it observes the node and another when it wins the size check.
6669 let timeCounter = 100
6770 jest . doMock ( '../../../../src/common/timing/now' , ( ) => ( {
6871 now : jest . fn ( ( ) => timeCounter ++ )
@@ -81,7 +84,8 @@ describe('trackMFEVitals', () => {
8184 describe ( 'FCP tracking' , ( ) => {
8285 it ( 'should track FCP when contentful element is added to MFE' , ( ) => {
8386 const vitals = trackMFEVitals ( 'test-mfe' , timings )
84- expect ( vitals . fcp ) . toBeNull ( )
87+ // The collector seeds FCP as soon as it sees the MFE marker on the page.
88+ expect ( vitals . fcp . value ) . toBe ( clockBaseline - 1 )
8589
8690 // Create a mock element with text content inside MFE container
8791 const mockContainer = {
@@ -107,7 +111,7 @@ describe('trackMFEVitals', () => {
107111 } ] ) )
108112
109113 expect ( vitals . fcp ) . toBeDefined ( )
110- expect ( vitals . fcp . value ) . toBe ( 10 )
114+ expect ( vitals . fcp . value ) . toBe ( clockBaseline - 1 )
111115 } )
112116
113117 it ( 'should use scriptStart as the timestamp anchor for FCP' , ( ) => {
@@ -132,7 +136,7 @@ describe('trackMFEVitals', () => {
132136
133137 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ mockElement ] } ] ) )
134138
135- expect ( vitals . fcp . value ) . toBe ( 10 )
139+ expect ( vitals . fcp . value ) . toBe ( clockBaseline - 1 )
136140 } )
137141 } )
138142
@@ -163,7 +167,7 @@ describe('trackMFEVitals', () => {
163167 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ mockElement ] } ] ) )
164168
165169 expect ( vitals . lcp ) . toBeDefined ( )
166- expect ( vitals . lcp . value ) . toBe ( 11 )
170+ expect ( vitals . lcp . value ) . toBe ( clockBaseline + 1 )
167171 } )
168172
169173 it ( 'should update LCP when larger element is added' , ( ) => {
@@ -204,8 +208,8 @@ describe('trackMFEVitals', () => {
204208
205209 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ largeElement ] } ] ) )
206210
207- expect ( firstLcp ) . toBe ( 11 )
208- expect ( vitals . lcp . value ) . toBe ( 12 )
211+ expect ( firstLcp ) . toBe ( clockBaseline + 1 )
212+ expect ( vitals . lcp . value ) . toBe ( clockBaseline + 2 )
209213 } )
210214
211215 it ( 'should extract URL from element background image' , ( ) => {
@@ -237,7 +241,7 @@ describe('trackMFEVitals', () => {
237241
238242 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ mockElement ] } ] ) )
239243
240- expect ( vitals . lcp . value ) . toBe ( 11 )
244+ expect ( vitals . lcp . value ) . toBe ( clockBaseline + 1 )
241245 } )
242246 } )
243247
@@ -352,7 +356,7 @@ describe('trackMFEVitals', () => {
352356
353357 const vitals = trackMFEVitals ( 'missing-mfe' , timings )
354358
355- expect ( vitals . cls ) . toBeNull ( )
359+ expect ( vitals . cls . value ) . toBeNull ( )
356360 } )
357361 } )
358362
@@ -491,12 +495,15 @@ describe('trackMFEVitals', () => {
491495 ]
492496 } )
493497
494- expect ( vitals . inp ) . toBeNull ( )
498+ expect ( vitals . inp . value ) . toBeNull ( )
495499 } )
496500 } )
497501
498502 describe ( 'Element scope validation' , ( ) => {
499503 it ( 'should only track elements within the specified MFE' , ( ) => {
504+ const mockGlobalScope = require ( '../../../../src/common/constants/runtime' ) . globalScope
505+ mockGlobalScope . document . querySelector = jest . fn ( ( ) => null )
506+
500507 const vitals = trackMFEVitals ( 'mfe-a' , timings )
501508
502509 const mfeBContainer = {
@@ -518,7 +525,7 @@ describe('trackMFEVitals', () => {
518525
519526 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ mfeBElement ] } ] ) )
520527
521- expect ( vitals . fcp ) . toBeNull ( )
528+ expect ( vitals . fcp . value ) . toBeNull ( )
522529 } )
523530
524531 it ( 'should track nested elements within MFE' , ( ) => {
@@ -545,9 +552,9 @@ describe('trackMFEVitals', () => {
545552 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ childElement ] } ] ) )
546553
547554 expect ( vitals . fcp ) . toBeDefined ( )
548- expect ( vitals . fcp . value ) . toBe ( 10 )
555+ expect ( vitals . fcp . value ) . toBe ( clockBaseline - 1 )
549556 expect ( vitals . lcp ) . toBeDefined ( )
550- expect ( vitals . lcp . value ) . toBe ( 11 )
557+ expect ( vitals . lcp . value ) . toBe ( clockBaseline + 1 )
551558 } )
552559 } )
553560
@@ -593,10 +600,11 @@ describe('trackMFEVitals', () => {
593600 it ( 'should return empty vitals when id is missing' , ( ) => {
594601 const vitals = trackMFEVitals ( '' , timings )
595602
596- expect ( vitals . fcp ) . toBeNull ( )
597- expect ( vitals . lcp ) . toBeNull ( )
598- expect ( vitals . cls ) . toBeNull ( )
599- expect ( vitals . inp ) . toBeNull ( )
603+ // The collector still returns the vitals shell, but the value getters are never populated.
604+ expect ( Object . getOwnPropertyDescriptor ( vitals . fcp , 'value' ) ?. get ) . toEqual ( expect . any ( Function ) )
605+ expect ( Object . getOwnPropertyDescriptor ( vitals . lcp , 'value' ) ?. get ) . toEqual ( expect . any ( Function ) )
606+ expect ( vitals . cls . value ) . toBeNull ( )
607+ expect ( vitals . inp . value ) . toBeNull ( )
600608 } )
601609
602610 it ( 'should handle elements without id or className' , ( ) => {
@@ -622,7 +630,7 @@ describe('trackMFEVitals', () => {
622630
623631 mutationCallbacks . forEach ( cb => cb ( [ { addedNodes : [ mockElement ] } ] ) )
624632
625- expect ( vitals . lcp . value ) . toBe ( 11 )
633+ expect ( vitals . lcp . value ) . toBe ( clockBaseline + 1 )
626634 } )
627635
628636 it ( 'should handle elements with className as object' , ( ) => {
0 commit comments