@@ -257,3 +257,137 @@ describe("BenchmarkRunner", () => {
257257 } ) ;
258258 } ) ;
259259} ) ;
260+
261+ describe ( "PageElement" , ( ) => {
262+ async function withPageElement ( configureFrame , callback ) {
263+ let fixtureNode ;
264+ let result ;
265+ const runner = new BenchmarkRunner (
266+ [
267+ {
268+ name : "PageElement fixture" ,
269+ enabled : true ,
270+ url : "about:blank" ,
271+ async prepare ( page ) {
272+ result = await callback ( page . getElementById ( "resize-target" ) , fixtureNode ) ;
273+ } ,
274+ tests : [ ] ,
275+ } ,
276+ ] ,
277+ { }
278+ ) ;
279+ sinon . stub ( SuiteRunner . prototype , "_loadFrame" ) . callsFake ( async function ( ) {
280+ fixtureNode = configureFrame ( this . frame ) ;
281+ } ) ;
282+ sinon . stub ( SuiteRunner . prototype , "_runSuite" ) . callsFake ( async ( ) => { } ) ;
283+ await runner . runAllSuites ( ) ;
284+ return result ;
285+ }
286+
287+ describe ( "observeResizeEvents" , ( ) => {
288+ // A fake ResizeObserver drives deliveries synchronously for exact-count assertions.
289+ async function createTracker ( ) {
290+ let deliver ;
291+ class FakeResizeObserver {
292+ constructor ( callback ) {
293+ deliver = ( ...widths ) => callback ( widths . map ( ( width ) => ( { contentBoxSize : [ { inlineSize : width } ] } ) ) ) ;
294+ }
295+ observe ( ) { }
296+ disconnect ( ) { }
297+ }
298+ return withPageElement (
299+ ( frame ) => {
300+ Object . defineProperty ( frame . contentWindow , "ResizeObserver" , { configurable : true , value : FakeResizeObserver } ) ;
301+ const node = frame . contentDocument . createElement ( "div" ) ;
302+ node . id = "resize-target" ;
303+ frame . contentDocument . body . appendChild ( node ) ;
304+ return node ;
305+ } ,
306+ ( element ) => ( {
307+ resizeEvents : element . observeResizeEvents ( ) ,
308+ deliver : ( ...widths ) => deliver ( ...widths ) ,
309+ } )
310+ ) ;
311+ }
312+
313+ it ( "treats the first delivery as the baseline without counting it" , async ( ) => {
314+ const { resizeEvents, deliver } = await createTracker ( ) ;
315+ deliver ( 200 ) ;
316+ await resizeEvents . ready ;
317+ expect ( resizeEvents . count ) . to . equal ( 0 ) ;
318+ } ) ;
319+
320+ it ( "counts each distinct width change exactly once" , async ( ) => {
321+ const { resizeEvents, deliver } = await createTracker ( ) ;
322+ deliver ( 200 ) ; // seed
323+ deliver ( 300 ) ;
324+ deliver ( 400 ) ;
325+ deliver ( 500 ) ;
326+ expect ( resizeEvents . count ) . to . equal ( 3 ) ;
327+ } ) ;
328+
329+ it ( "does not count deliveries that report the same width" , async ( ) => {
330+ const { resizeEvents, deliver } = await createTracker ( ) ;
331+ deliver ( 200 ) ; // seed
332+ deliver ( 300 ) ;
333+ deliver ( 300 ) ;
334+ deliver ( 300 ) ;
335+ expect ( resizeEvents . count ) . to . equal ( 1 ) ;
336+ } ) ;
337+ } ) ;
338+ describe ( "observeResizeEvents with a real ResizeObserver" , ( ) => {
339+ // border-box width minus 20px padding and 10px border on each side.
340+ const BORDER_BOX_INSET = 60 ;
341+
342+ function appendBox ( document , borderBoxWidth ) {
343+ const div = document . createElement ( "div" ) ;
344+ div . id = "resize-target" ;
345+ div . style . cssText = `box-sizing: border-box; padding: 20px; border: 10px solid black; position: absolute; top: -9999px; left: -9999px; height: 50px; width: ${ borderBoxWidth } px;` ;
346+ document . body . appendChild ( div ) ;
347+ return div ;
348+ }
349+
350+ // Resolves once a probe observer sees the expected content-box width. The
351+ // tracker is created first, so it is notified first in the same frame.
352+ function waitForContentWidth ( element , expectedWidth , timeoutMs = 2000 ) {
353+ return new Promise ( ( resolve , reject ) => {
354+ let observer ;
355+ const timeout = setTimeout ( ( ) => {
356+ observer . disconnect ( ) ;
357+ reject ( new Error ( `Timed out waiting for content-box width ${ expectedWidth } px` ) ) ;
358+ } , timeoutMs ) ;
359+ const ResizeObserver = element . ownerDocument . defaultView . ResizeObserver ;
360+ observer = new ResizeObserver ( ( entries ) => {
361+ for ( const entry of entries ) {
362+ const contentBoxSize = entry . contentBoxSize ;
363+ const inlineSize = Array . isArray ( contentBoxSize ) ? contentBoxSize [ 0 ] ?. inlineSize : contentBoxSize ?. inlineSize ;
364+ const width = inlineSize ?? entry . contentRect . width ;
365+ if ( width === expectedWidth ) {
366+ clearTimeout ( timeout ) ;
367+ observer . disconnect ( ) ;
368+ resolve ( ) ;
369+ return ;
370+ }
371+ }
372+ } ) ;
373+ observer . observe ( element ) ;
374+ } ) ;
375+ }
376+
377+ it ( "counts each distinct width change exactly once" , async ( ) => {
378+ await withPageElement (
379+ ( frame ) => appendBox ( frame . contentDocument , 200 ) ,
380+ async ( element , div ) => {
381+ const resizeEvents = element . observeResizeEvents ( ) ;
382+ await resizeEvents . ready ;
383+ div . style . width = "300px" ;
384+ await waitForContentWidth ( div , 300 - BORDER_BOX_INSET ) ;
385+ div . style . width = "400px" ;
386+ await waitForContentWidth ( div , 400 - BORDER_BOX_INSET ) ;
387+ expect ( resizeEvents . count ) . to . equal ( 2 ) ;
388+ resizeEvents . disconnect ( ) ;
389+ }
390+ ) ;
391+ } ) ;
392+ } ) ;
393+ } ) ;
0 commit comments