@@ -12,167 +12,172 @@ import {
1212} from './virtualList'
1313
1414describe ( 'variable row measurements' , ( ) => {
15- it . each ( [
16- 'list ' ,
17- 'table' ,
18- ] as const ) ( 'keeps %s rows observed after the window updates' , async ( kind ) => {
19- const cleanupGlobals = ensureDomGlobals ( )
20- const cleanupDom = createDom (
21- '<html><body><div id="app"></div></body></html>' ,
22- )
23- const frames = new Map < number , FrameRequestCallback > ( )
24- let frameId = 0
25- const observers : TestResizeObserver [ ] = [ ]
26- const heights = new Map < Element , number > ( )
27- class TestResizeObserver {
28- readonly elements = new Set < Element > ( )
29- constructor ( readonly callback : ResizeObserverCallback ) {
30- observers . push ( this )
31- }
32- observe ( element : Element ) {
33- this . elements . add ( element )
34- element . getBoundingClientRect = ( ) =>
35- ( { height : heights . get ( element ) ?? 40 } ) as DOMRect
36- }
37- unobserve ( element : Element ) {
38- this . elements . delete ( element )
39- }
40- disconnect ( ) {
41- this . elements . clear ( )
42- }
43- resize ( element : Element ) {
44- if ( this . elements . has ( element ) )
45- this . callback (
46- [ { target : element } as ResizeObserverEntry ] ,
47- this as unknown as ResizeObserver ,
48- )
15+ it . each ( [ 'list' , 'table' ] as const ) (
16+ 'keeps %s rows observed after the window updates ' ,
17+ async ( kind ) => {
18+ const cleanupGlobals = ensureDomGlobals ( )
19+ const cleanupDom = createDom (
20+ '<html><body><div id="app"></div></body></html>' ,
21+ )
22+ const frames = new Map < number , FrameRequestCallback > ( )
23+ let frameId = 0
24+ const observers : TestResizeObserver [ ] = [ ]
25+ const heights = new Map < Element , number > ( )
26+ class TestResizeObserver {
27+ readonly elements = new Set < Element > ( )
28+ constructor ( readonly callback : ResizeObserverCallback ) {
29+ observers . push ( this )
30+ }
31+ observe ( element : Element ) {
32+ this . elements . add ( element )
33+ element . getBoundingClientRect = ( ) =>
34+ ( { height : heights . get ( element ) ?? 40 } ) as DOMRect
35+ }
36+ unobserve ( element : Element ) {
37+ this . elements . delete ( element )
38+ }
39+ disconnect ( ) {
40+ this . elements . clear ( )
41+ }
42+ resize ( element : Element ) {
43+ if ( this . elements . has ( element ) )
44+ this . callback (
45+ [ { target : element } as ResizeObserverEntry ] ,
46+ this as unknown as ResizeObserver ,
47+ )
48+ }
4949 }
50- }
51- vi . stubGlobal ( 'ResizeObserver' , TestResizeObserver )
52- vi . stubGlobal ( 'requestAnimationFrame' , ( callback : FrameRequestCallback ) => {
53- frames . set ( ++ frameId , callback )
54- return frameId
55- } )
56- vi . stubGlobal ( 'cancelAnimationFrame' , ( id : number ) => frames . delete ( id ) )
57- const flush = async ( ) => {
58- for ( let pass = 0 ; pass < 20 ; pass ++ ) {
59- await Promise . resolve ( )
60- if ( ! frames . size ) return
61- const pending = [ ...frames . values ( ) ]
62- frames . clear ( )
63- for ( const callback of pending ) callback ( 0 )
50+ vi . stubGlobal ( 'ResizeObserver' , TestResizeObserver )
51+ vi . stubGlobal (
52+ 'requestAnimationFrame' ,
53+ ( callback : FrameRequestCallback ) => {
54+ frames . set ( ++ frameId , callback )
55+ return frameId
56+ } ,
57+ )
58+ vi . stubGlobal ( 'cancelAnimationFrame' , ( id : number ) => frames . delete ( id ) )
59+ const flush = async ( ) => {
60+ for ( let pass = 0 ; pass < 20 ; pass ++ ) {
61+ await Promise . resolve ( )
62+ if ( ! frames . size ) return
63+ const pending = [ ...frames . values ( ) ]
64+ frames . clear ( )
65+ for ( const callback of pending ) callback ( 0 )
66+ }
67+ throw new Error ( 'Measurement did not settle' )
6468 }
65- throw new Error ( 'Measurement did not settle' )
66- }
67- let context : VariableVirtualList | VariableVirtualTable | undefined
68- const recordContext = <
69- T extends VariableVirtualList | VariableVirtualTable ,
70- > (
71- candidate : Component < T > ,
72- ) => {
73- const original = candidate . context
74- candidate . context = ( head ) => {
75- const resolved = original ( head )
76- context = resolved
77- return resolved
69+ let context : VariableVirtualList | VariableVirtualTable | undefined
70+ const recordContext = <
71+ T extends VariableVirtualList | VariableVirtualTable ,
72+ > (
73+ candidate : Component < T > ,
74+ ) => {
75+ const original = candidate . context
76+ candidate . context = ( head ) => {
77+ const resolved = original ( head )
78+ context = resolved
79+ return resolved
80+ }
81+ return candidate
7882 }
79- return candidate
80- }
81- const component =
82- kind === 'list'
83- ? recordContext ( defineVirtualListComponents ( ) . variableVirtualList )
84- : recordContext (
85- defineVariableVirtualTableComponents ( ) . variableVirtualTable ,
86- )
87- const viewportHeight = ref ( 160 )
88- let rowMounts = 0
89- const app = createApp (
90- {
91- components : {
92- Measured : component ,
93- Row : defineComponent (
94- kind === 'list'
95- ? '<div>{{ item.label }}</div>'
96- : '<tr><td>{{ item.label }}</td></tr>' ,
97- {
98- props : [ 'item' , 'index' ] ,
99- context : ( head ) => {
100- rowMounts ++
101- return head . props
102- } ,
103- } ,
104- ) ,
105- } ,
106- tsSsgContext : createTestContext ( ) ,
107- items : Array . from ( { length : 50000 } , ( _ , index ) => ( {
108- label : `Row ${ index } ` ,
109- } ) ) ,
110- viewportHeight,
111- } ,
112- {
113- selector : '#app' ,
114- template :
115- '<Measured :items="items" :height="viewportHeight" estimateHeight="80" overscan="2" rowComponent="Row" />' ,
116- } ,
117- )
118- try {
119- await flush ( )
120- const beforeRows = context ?. visibleRows ?.( ) ?? [ ]
121- expect ( beforeRows . length ) . toBeGreaterThan ( 0 )
122- const firstRow = beforeRows [ 0 ]
123- const elements = observers . flatMap ( ( observer ) => [ ...observer . elements ] )
124- expect ( elements . length ) . toBeGreaterThan ( 0 )
125- if ( kind === 'table' )
126- expect ( elements . every ( ( element ) => element . tagName === 'TR' ) ) . toBe ( true )
127- const total = ( ) =>
83+ const component =
12884 kind === 'list'
129- ? Number . parseFloat (
130- ( context as VariableVirtualList ) . spacerStyle ?.( ) . height ?? '0' ,
131- )
132- : Number . parseFloat (
133- ( context as VariableVirtualTable ) . bottomSpacerStyle ?.( ) . height ??
134- '0' ,
85+ ? recordContext ( defineVirtualListComponents ( ) . variableVirtualList )
86+ : recordContext (
87+ defineVariableVirtualTableComponents ( ) . variableVirtualTable ,
13588 )
136- const initial = total ( )
137- expect ( initial ) . toBeLessThan ( 50000 * 80 )
138- viewportHeight ( 200 )
139- await flush ( )
140- expect ( context ?. visibleRows ?.( ) [ 0 ] ) . toBe ( firstRow )
141- const element = elements [ 0 ]
142- const beforeResize = total ( )
143- heights . set ( element , 120 )
144- for ( const observer of observers ) observer . resize ( element )
145- await flush ( )
146- expect ( total ( ) ) . toBeGreaterThan ( beforeResize )
147- expect ( context ?. visibleRows ?.( ) [ 0 ] ) . toBe ( firstRow )
148- const emittedSizes : number [ ] = [ ]
149- const visibleRows = context ?. visibleRows
150- const viewport = context ?. viewportElement ?.( )
151- if ( ! visibleRows || ! viewport )
152- throw new Error ( 'Virtual rows and viewport were not initialized' )
153- const stop = observe ( visibleRows , ( rows ) => {
154- emittedSizes . push ( rows . length )
155- } )
156- for ( const position of [ 1000000 , 1900000 , 200000 , 0 ] ) {
157- const beforeJump = rowMounts
158- viewport . scrollTop = position
159- viewport . dispatchEvent ( new Event ( 'scroll' ) )
160- expect ( visibleRows ( ) . length ) . toBeLessThan ( 20 )
161- expect ( rowMounts - beforeJump ) . toBeLessThan ( 40 )
162- }
163- stop ( )
164- expect ( emittedSizes . length ) . toBeGreaterThan ( 0 )
165- // Checking only the final window misses thousands of transient mounts.
166- expect ( Math . max ( ...emittedSizes ) ) . toBeLessThan ( 20 )
167- app . unbind ( )
168- expect ( observers . every ( ( observer ) => observer . elements . size === 0 ) ) . toBe (
169- true ,
89+ const viewportHeight = ref ( 160 )
90+ let rowMounts = 0
91+ const app = createApp (
92+ {
93+ components : {
94+ Measured : component ,
95+ Row : defineComponent (
96+ kind === 'list'
97+ ? '<div>{{ item.label }}</div>'
98+ : '<tr><td>{{ item.label }}</td></tr>' ,
99+ {
100+ props : [ 'item' , 'index' ] ,
101+ context : ( head ) => {
102+ rowMounts ++
103+ return head . props
104+ } ,
105+ } ,
106+ ) ,
107+ } ,
108+ tsSsgContext : createTestContext ( ) ,
109+ items : Array . from ( { length : 50000 } , ( _ , index ) => ( {
110+ label : `Row ${ index } ` ,
111+ } ) ) ,
112+ viewportHeight,
113+ } ,
114+ {
115+ selector : '#app' ,
116+ template :
117+ '<Measured :items="items" :height="viewportHeight" estimateHeight="80" overscan="2" rowComponent="Row" />' ,
118+ } ,
170119 )
171- } finally {
172- app . unbind ( )
173- vi . unstubAllGlobals ( )
174- cleanupDom ( )
175- cleanupGlobals ( )
176- }
177- } )
120+ try {
121+ await flush ( )
122+ const beforeRows = context ?. visibleRows ?.( ) ?? [ ]
123+ expect ( beforeRows . length ) . toBeGreaterThan ( 0 )
124+ const firstRow = beforeRows [ 0 ]
125+ const elements = observers . flatMap ( ( observer ) => [ ...observer . elements ] )
126+ expect ( elements . length ) . toBeGreaterThan ( 0 )
127+ if ( kind === 'table' )
128+ expect ( elements . every ( ( element ) => element . tagName === 'TR' ) ) . toBe (
129+ true ,
130+ )
131+ const total = ( ) =>
132+ kind === 'list'
133+ ? Number . parseFloat (
134+ ( context as VariableVirtualList ) . spacerStyle ?.( ) . height ?? '0' ,
135+ )
136+ : Number . parseFloat (
137+ ( context as VariableVirtualTable ) . bottomSpacerStyle ?.( )
138+ . height ?? '0' ,
139+ )
140+ const initial = total ( )
141+ expect ( initial ) . toBeLessThan ( 50000 * 80 )
142+ viewportHeight ( 200 )
143+ await flush ( )
144+ expect ( context ?. visibleRows ?.( ) [ 0 ] ) . toBe ( firstRow )
145+ const element = elements [ 0 ]
146+ const beforeResize = total ( )
147+ heights . set ( element , 120 )
148+ for ( const observer of observers ) observer . resize ( element )
149+ await flush ( )
150+ expect ( total ( ) ) . toBeGreaterThan ( beforeResize )
151+ expect ( context ?. visibleRows ?.( ) [ 0 ] ) . toBe ( firstRow )
152+ const emittedSizes : number [ ] = [ ]
153+ const visibleRows = context ?. visibleRows
154+ const viewport = context ?. viewportElement ?.( )
155+ if ( ! visibleRows || ! viewport )
156+ throw new Error ( 'Virtual rows and viewport were not initialized' )
157+ const stop = observe ( visibleRows , ( rows ) => {
158+ emittedSizes . push ( rows . length )
159+ } )
160+ for ( const position of [ 1000000 , 1900000 , 200000 , 0 ] ) {
161+ const beforeJump = rowMounts
162+ viewport . scrollTop = position
163+ viewport . dispatchEvent ( new Event ( 'scroll' ) )
164+ expect ( visibleRows ( ) . length ) . toBeLessThan ( 20 )
165+ expect ( rowMounts - beforeJump ) . toBeLessThan ( 40 )
166+ }
167+ stop ( )
168+ expect ( emittedSizes . length ) . toBeGreaterThan ( 0 )
169+ // Checking only the final window misses thousands of transient mounts.
170+ expect ( Math . max ( ...emittedSizes ) ) . toBeLessThan ( 20 )
171+ app . unbind ( )
172+ expect (
173+ observers . every ( ( observer ) => observer . elements . size === 0 ) ,
174+ ) . toBe ( true )
175+ } finally {
176+ app . unbind ( )
177+ vi . unstubAllGlobals ( )
178+ cleanupDom ( )
179+ cleanupGlobals ( )
180+ }
181+ } ,
182+ )
178183} )
0 commit comments