@pyreon/virtual 0.24.5 → 0.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -8
- package/src/index.ts +0 -32
- package/src/manifest.ts +0 -129
- package/src/tests/manifest-snapshot.test.ts +0 -87
- package/src/tests/virtual-comprehensive.test.tsx +0 -564
- package/src/tests/virtual.test.tsx +0 -612
- package/src/use-virtualizer.ts +0 -119
- package/src/use-window-virtualizer.ts +0 -101
|
@@ -1,612 +0,0 @@
|
|
|
1
|
-
import { signal } from '@pyreon/reactivity'
|
|
2
|
-
import { mount } from '@pyreon/runtime-dom'
|
|
3
|
-
import { useVirtualizer, useWindowVirtualizer } from '../index'
|
|
4
|
-
|
|
5
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
function mountWith<T>(fn: () => T): { result: T; unmount: () => void } {
|
|
8
|
-
let result: T | undefined
|
|
9
|
-
const el = document.createElement('div')
|
|
10
|
-
document.body.appendChild(el)
|
|
11
|
-
const Wrapper = () => {
|
|
12
|
-
result = fn()
|
|
13
|
-
return null
|
|
14
|
-
}
|
|
15
|
-
const unmount = mount(<Wrapper />, el)
|
|
16
|
-
return {
|
|
17
|
-
result: result!,
|
|
18
|
-
unmount: () => {
|
|
19
|
-
unmount()
|
|
20
|
-
el.remove()
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Create a mock scroll container with a known size. */
|
|
26
|
-
function createScrollContainer(height = 200): HTMLDivElement {
|
|
27
|
-
const container = document.createElement('div')
|
|
28
|
-
// happy-dom doesn't have real layout, but we can set properties
|
|
29
|
-
Object.defineProperty(container, 'offsetHeight', { value: height })
|
|
30
|
-
Object.defineProperty(container, 'offsetWidth', { value: 300 })
|
|
31
|
-
Object.defineProperty(container, 'scrollHeight', { value: 10000 })
|
|
32
|
-
Object.defineProperty(container, 'clientHeight', { value: height })
|
|
33
|
-
document.body.appendChild(container)
|
|
34
|
-
return container
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ─── useVirtualizer ──────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
describe('useVirtualizer', () => {
|
|
40
|
-
it('creates a virtualizer with virtual items', () => {
|
|
41
|
-
const container = createScrollContainer()
|
|
42
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
43
|
-
useVirtualizer(() => ({
|
|
44
|
-
count: 1000,
|
|
45
|
-
getScrollElement: () => container,
|
|
46
|
-
estimateSize: () => 35,
|
|
47
|
-
})),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
// Should have some virtual items based on container size + overscan
|
|
51
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
52
|
-
expect(Array.isArray(virt.virtualItems())).toBe(true)
|
|
53
|
-
unmount()
|
|
54
|
-
container.remove()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('returns correct total size', () => {
|
|
58
|
-
const container = createScrollContainer()
|
|
59
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
60
|
-
useVirtualizer(() => ({
|
|
61
|
-
count: 100,
|
|
62
|
-
getScrollElement: () => container,
|
|
63
|
-
estimateSize: () => 50,
|
|
64
|
-
})),
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
// 100 items * 50px = 5000px
|
|
68
|
-
expect(virt.totalSize()).toBe(5000)
|
|
69
|
-
unmount()
|
|
70
|
-
container.remove()
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('reactive count — updates when count signal changes', () => {
|
|
74
|
-
const container = createScrollContainer()
|
|
75
|
-
const count = signal(100)
|
|
76
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
77
|
-
useVirtualizer(() => ({
|
|
78
|
-
count: count(),
|
|
79
|
-
getScrollElement: () => container,
|
|
80
|
-
estimateSize: () => 50,
|
|
81
|
-
})),
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
expect(virt.totalSize()).toBe(5000)
|
|
85
|
-
|
|
86
|
-
count.set(200)
|
|
87
|
-
expect(virt.totalSize()).toBe(10000)
|
|
88
|
-
unmount()
|
|
89
|
-
container.remove()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('reactive estimateSize — updates total size', () => {
|
|
93
|
-
const container = createScrollContainer()
|
|
94
|
-
const itemSize = signal(50)
|
|
95
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
96
|
-
useVirtualizer(() => ({
|
|
97
|
-
count: 100,
|
|
98
|
-
getScrollElement: () => container,
|
|
99
|
-
estimateSize: () => itemSize(),
|
|
100
|
-
})),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
expect(virt.totalSize()).toBe(5000)
|
|
104
|
-
|
|
105
|
-
itemSize.set(100)
|
|
106
|
-
// Must call measure() to invalidate the measurement cache when estimateSize changes
|
|
107
|
-
virt.instance.measure()
|
|
108
|
-
expect(virt.totalSize()).toBe(10000)
|
|
109
|
-
unmount()
|
|
110
|
-
container.remove()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('exposes the virtualizer instance', () => {
|
|
114
|
-
const container = createScrollContainer()
|
|
115
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
116
|
-
useVirtualizer(() => ({
|
|
117
|
-
count: 50,
|
|
118
|
-
getScrollElement: () => container,
|
|
119
|
-
estimateSize: () => 40,
|
|
120
|
-
})),
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
expect(virt.instance).toBeDefined()
|
|
124
|
-
expect(typeof virt.instance.scrollToIndex).toBe('function')
|
|
125
|
-
expect(typeof virt.instance.scrollToOffset).toBe('function')
|
|
126
|
-
expect(typeof virt.instance.measureElement).toBe('function')
|
|
127
|
-
unmount()
|
|
128
|
-
container.remove()
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('virtual items have correct structure', () => {
|
|
132
|
-
const container = createScrollContainer()
|
|
133
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
134
|
-
useVirtualizer(() => ({
|
|
135
|
-
count: 1000,
|
|
136
|
-
getScrollElement: () => container,
|
|
137
|
-
estimateSize: () => 35,
|
|
138
|
-
})),
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
const items = virt.virtualItems()
|
|
142
|
-
if (items.length > 0) {
|
|
143
|
-
const item = items[0]!
|
|
144
|
-
expect(typeof item.index).toBe('number')
|
|
145
|
-
expect(typeof item.start).toBe('number')
|
|
146
|
-
expect(typeof item.end).toBe('number')
|
|
147
|
-
expect(typeof item.size).toBe('number')
|
|
148
|
-
expect(typeof item.key).toBeDefined()
|
|
149
|
-
}
|
|
150
|
-
unmount()
|
|
151
|
-
container.remove()
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('overscan controls extra items rendered', () => {
|
|
155
|
-
const container = createScrollContainer(200)
|
|
156
|
-
const { result: small, unmount: unmount1 } = mountWith(() =>
|
|
157
|
-
useVirtualizer(() => ({
|
|
158
|
-
count: 1000,
|
|
159
|
-
getScrollElement: () => container,
|
|
160
|
-
estimateSize: () => 35,
|
|
161
|
-
overscan: 0,
|
|
162
|
-
})),
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
const { result: large, unmount: unmount2 } = mountWith(() =>
|
|
166
|
-
useVirtualizer(() => ({
|
|
167
|
-
count: 1000,
|
|
168
|
-
getScrollElement: () => container,
|
|
169
|
-
estimateSize: () => 35,
|
|
170
|
-
overscan: 10,
|
|
171
|
-
})),
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
// More overscan = more virtual items
|
|
175
|
-
expect(large.virtualItems().length).toBeGreaterThanOrEqual(small.virtualItems().length)
|
|
176
|
-
unmount1()
|
|
177
|
-
unmount2()
|
|
178
|
-
container.remove()
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('gap option affects total size', () => {
|
|
182
|
-
const container = createScrollContainer()
|
|
183
|
-
const { result: noGap, unmount: unmount1 } = mountWith(() =>
|
|
184
|
-
useVirtualizer(() => ({
|
|
185
|
-
count: 10,
|
|
186
|
-
getScrollElement: () => container,
|
|
187
|
-
estimateSize: () => 50,
|
|
188
|
-
gap: 0,
|
|
189
|
-
})),
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
const { result: withGap, unmount: unmount2 } = mountWith(() =>
|
|
193
|
-
useVirtualizer(() => ({
|
|
194
|
-
count: 10,
|
|
195
|
-
getScrollElement: () => container,
|
|
196
|
-
estimateSize: () => 50,
|
|
197
|
-
gap: 10,
|
|
198
|
-
})),
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
// 10 items * 50 = 500 vs 10 items * 50 + 9 gaps * 10 = 590
|
|
202
|
-
expect(noGap.totalSize()).toBe(500)
|
|
203
|
-
expect(withGap.totalSize()).toBe(590)
|
|
204
|
-
unmount1()
|
|
205
|
-
unmount2()
|
|
206
|
-
container.remove()
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('horizontal mode works', () => {
|
|
210
|
-
const container = createScrollContainer()
|
|
211
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
212
|
-
useVirtualizer(() => ({
|
|
213
|
-
count: 100,
|
|
214
|
-
getScrollElement: () => container,
|
|
215
|
-
estimateSize: () => 100,
|
|
216
|
-
horizontal: true,
|
|
217
|
-
})),
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
expect(virt.totalSize()).toBe(10000)
|
|
221
|
-
expect(virt.virtualItems().length).toBeGreaterThan(0)
|
|
222
|
-
unmount()
|
|
223
|
-
container.remove()
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
it('padding affects total size', () => {
|
|
227
|
-
const container = createScrollContainer()
|
|
228
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
229
|
-
useVirtualizer(() => ({
|
|
230
|
-
count: 10,
|
|
231
|
-
getScrollElement: () => container,
|
|
232
|
-
estimateSize: () => 50,
|
|
233
|
-
paddingStart: 20,
|
|
234
|
-
paddingEnd: 30,
|
|
235
|
-
})),
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
// 10 * 50 + 20 + 30 = 550
|
|
239
|
-
expect(virt.totalSize()).toBe(550)
|
|
240
|
-
unmount()
|
|
241
|
-
container.remove()
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
it('isScrolling starts as false', () => {
|
|
245
|
-
const container = createScrollContainer()
|
|
246
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
247
|
-
useVirtualizer(() => ({
|
|
248
|
-
count: 100,
|
|
249
|
-
getScrollElement: () => container,
|
|
250
|
-
estimateSize: () => 50,
|
|
251
|
-
})),
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
expect(virt.isScrolling()).toBe(false)
|
|
255
|
-
unmount()
|
|
256
|
-
container.remove()
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
it('enabled: false produces empty virtual items', () => {
|
|
260
|
-
const container = createScrollContainer()
|
|
261
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
262
|
-
useVirtualizer(() => ({
|
|
263
|
-
count: 100,
|
|
264
|
-
getScrollElement: () => container,
|
|
265
|
-
estimateSize: () => 50,
|
|
266
|
-
enabled: false,
|
|
267
|
-
})),
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
expect(virt.virtualItems()).toHaveLength(0)
|
|
271
|
-
expect(virt.totalSize()).toBe(0)
|
|
272
|
-
unmount()
|
|
273
|
-
container.remove()
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
it('onChange callback updates signals when triggered', () => {
|
|
277
|
-
const container = createScrollContainer()
|
|
278
|
-
const onChangeSpy = vi.fn()
|
|
279
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
280
|
-
useVirtualizer(() => ({
|
|
281
|
-
count: 100,
|
|
282
|
-
getScrollElement: () => container,
|
|
283
|
-
estimateSize: () => 50,
|
|
284
|
-
onChange: onChangeSpy,
|
|
285
|
-
})),
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
// Manually invoke the instance's onChange to simulate a scroll/resize event
|
|
289
|
-
// This exercises the constructor's onChange callback (lines 76-81)
|
|
290
|
-
const onChange = virt.instance.options.onChange
|
|
291
|
-
if (onChange) {
|
|
292
|
-
onChange(virt.instance, false)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
296
|
-
expect(typeof virt.totalSize()).toBe('number')
|
|
297
|
-
expect(typeof virt.isScrolling()).toBe('boolean')
|
|
298
|
-
// The user's onChange should have been forwarded
|
|
299
|
-
expect(onChangeSpy).toHaveBeenCalled()
|
|
300
|
-
unmount()
|
|
301
|
-
container.remove()
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it('onChange callback works without user-provided onChange', () => {
|
|
305
|
-
const container = createScrollContainer()
|
|
306
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
307
|
-
useVirtualizer(() => ({
|
|
308
|
-
count: 100,
|
|
309
|
-
getScrollElement: () => container,
|
|
310
|
-
estimateSize: () => 50,
|
|
311
|
-
// No onChange — exercises the resolvedOptions.onChange?.() optional chain
|
|
312
|
-
})),
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
// Trigger onChange without a user handler
|
|
316
|
-
const onChange = virt.instance.options.onChange
|
|
317
|
-
if (onChange) {
|
|
318
|
-
onChange(virt.instance, false)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
322
|
-
unmount()
|
|
323
|
-
container.remove()
|
|
324
|
-
})
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
// ─── useWindowVirtualizer ─────────────────────────────────────────────────────
|
|
328
|
-
|
|
329
|
-
describe('useWindowVirtualizer', () => {
|
|
330
|
-
beforeEach(() => {
|
|
331
|
-
// Ensure window has layout-like properties for happy-dom
|
|
332
|
-
Object.defineProperty(window, 'innerHeight', {
|
|
333
|
-
value: 768,
|
|
334
|
-
writable: true,
|
|
335
|
-
configurable: true,
|
|
336
|
-
})
|
|
337
|
-
Object.defineProperty(window, 'innerWidth', {
|
|
338
|
-
value: 1024,
|
|
339
|
-
writable: true,
|
|
340
|
-
configurable: true,
|
|
341
|
-
})
|
|
342
|
-
Object.defineProperty(window, 'scrollY', {
|
|
343
|
-
value: 0,
|
|
344
|
-
writable: true,
|
|
345
|
-
configurable: true,
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('creates an instance and returns virtualItems, totalSize, isScrolling signals', () => {
|
|
350
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
351
|
-
useWindowVirtualizer(() => ({
|
|
352
|
-
count: 1000,
|
|
353
|
-
estimateSize: () => 35,
|
|
354
|
-
})),
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
expect(virt.instance).toBeDefined()
|
|
358
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
359
|
-
expect(Array.isArray(virt.virtualItems())).toBe(true)
|
|
360
|
-
expect(typeof virt.totalSize()).toBe('number')
|
|
361
|
-
expect(virt.isScrolling()).toBe(false)
|
|
362
|
-
unmount()
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
it('returns correct total size', () => {
|
|
366
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
367
|
-
useWindowVirtualizer(() => ({
|
|
368
|
-
count: 100,
|
|
369
|
-
estimateSize: () => 50,
|
|
370
|
-
})),
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
expect(virt.totalSize()).toBe(5000)
|
|
374
|
-
unmount()
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
it('reactive count — updates when count signal changes', () => {
|
|
378
|
-
const count = signal(100)
|
|
379
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
380
|
-
useWindowVirtualizer(() => ({
|
|
381
|
-
count: count(),
|
|
382
|
-
estimateSize: () => 50,
|
|
383
|
-
})),
|
|
384
|
-
)
|
|
385
|
-
|
|
386
|
-
expect(virt.totalSize()).toBe(5000)
|
|
387
|
-
|
|
388
|
-
count.set(200)
|
|
389
|
-
expect(virt.totalSize()).toBe(10000)
|
|
390
|
-
unmount()
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
it('reactive estimateSize — updates total size after measure()', () => {
|
|
394
|
-
const itemSize = signal(50)
|
|
395
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
396
|
-
useWindowVirtualizer(() => ({
|
|
397
|
-
count: 100,
|
|
398
|
-
estimateSize: () => itemSize(),
|
|
399
|
-
})),
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
expect(virt.totalSize()).toBe(5000)
|
|
403
|
-
|
|
404
|
-
itemSize.set(100)
|
|
405
|
-
virt.instance.measure()
|
|
406
|
-
expect(virt.totalSize()).toBe(10000)
|
|
407
|
-
unmount()
|
|
408
|
-
})
|
|
409
|
-
|
|
410
|
-
it('exposes instance methods (scrollToIndex, scrollToOffset, measureElement)', () => {
|
|
411
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
412
|
-
useWindowVirtualizer(() => ({
|
|
413
|
-
count: 50,
|
|
414
|
-
estimateSize: () => 40,
|
|
415
|
-
})),
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
expect(typeof virt.instance.scrollToIndex).toBe('function')
|
|
419
|
-
expect(typeof virt.instance.scrollToOffset).toBe('function')
|
|
420
|
-
expect(typeof virt.instance.measureElement).toBe('function')
|
|
421
|
-
unmount()
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
it('virtual items have correct structure', () => {
|
|
425
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
426
|
-
useWindowVirtualizer(() => ({
|
|
427
|
-
count: 1000,
|
|
428
|
-
estimateSize: () => 35,
|
|
429
|
-
})),
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
const items = virt.virtualItems()
|
|
433
|
-
if (items.length > 0) {
|
|
434
|
-
const item = items[0]!
|
|
435
|
-
expect(typeof item.index).toBe('number')
|
|
436
|
-
expect(typeof item.start).toBe('number')
|
|
437
|
-
expect(typeof item.end).toBe('number')
|
|
438
|
-
expect(typeof item.size).toBe('number')
|
|
439
|
-
expect(typeof item.key).toBeDefined()
|
|
440
|
-
}
|
|
441
|
-
unmount()
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
it('gap option affects total size', () => {
|
|
445
|
-
const { result: noGap, unmount: unmount1 } = mountWith(() =>
|
|
446
|
-
useWindowVirtualizer(() => ({
|
|
447
|
-
count: 10,
|
|
448
|
-
estimateSize: () => 50,
|
|
449
|
-
gap: 0,
|
|
450
|
-
})),
|
|
451
|
-
)
|
|
452
|
-
|
|
453
|
-
const { result: withGap, unmount: unmount2 } = mountWith(() =>
|
|
454
|
-
useWindowVirtualizer(() => ({
|
|
455
|
-
count: 10,
|
|
456
|
-
estimateSize: () => 50,
|
|
457
|
-
gap: 10,
|
|
458
|
-
})),
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
// 10 items * 50 = 500 vs 10 items * 50 + 9 gaps * 10 = 590
|
|
462
|
-
expect(noGap.totalSize()).toBe(500)
|
|
463
|
-
expect(withGap.totalSize()).toBe(590)
|
|
464
|
-
unmount1()
|
|
465
|
-
unmount2()
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
it('padding affects total size', () => {
|
|
469
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
470
|
-
useWindowVirtualizer(() => ({
|
|
471
|
-
count: 10,
|
|
472
|
-
estimateSize: () => 50,
|
|
473
|
-
paddingStart: 20,
|
|
474
|
-
paddingEnd: 30,
|
|
475
|
-
})),
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
// 10 * 50 + 20 + 30 = 550
|
|
479
|
-
expect(virt.totalSize()).toBe(550)
|
|
480
|
-
unmount()
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
it('horizontal mode works', () => {
|
|
484
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
485
|
-
useWindowVirtualizer(() => ({
|
|
486
|
-
count: 100,
|
|
487
|
-
estimateSize: () => 100,
|
|
488
|
-
horizontal: true,
|
|
489
|
-
})),
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
expect(virt.totalSize()).toBe(10000)
|
|
493
|
-
expect(virt.virtualItems().length).toBeGreaterThan(0)
|
|
494
|
-
unmount()
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
it('enabled: false produces empty virtual items', () => {
|
|
498
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
499
|
-
useWindowVirtualizer(() => ({
|
|
500
|
-
count: 100,
|
|
501
|
-
estimateSize: () => 50,
|
|
502
|
-
enabled: false,
|
|
503
|
-
})),
|
|
504
|
-
)
|
|
505
|
-
|
|
506
|
-
expect(virt.virtualItems()).toHaveLength(0)
|
|
507
|
-
expect(virt.totalSize()).toBe(0)
|
|
508
|
-
unmount()
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
it('calls user-provided onChange callback', () => {
|
|
512
|
-
const onChangeSpy = vi.fn()
|
|
513
|
-
const count = signal(10)
|
|
514
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
515
|
-
useWindowVirtualizer(() => ({
|
|
516
|
-
count: count(),
|
|
517
|
-
estimateSize: () => 50,
|
|
518
|
-
onChange: onChangeSpy,
|
|
519
|
-
})),
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
// Trigger a reactive update which calls setOptions + _willUpdate
|
|
523
|
-
count.set(20)
|
|
524
|
-
// The onChange may be called during the update cycle
|
|
525
|
-
// At minimum, the virtualizer should still work correctly
|
|
526
|
-
expect(virt.totalSize()).toBe(1000)
|
|
527
|
-
unmount()
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
it('overscan controls extra items rendered', () => {
|
|
531
|
-
const { result: small, unmount: unmount1 } = mountWith(() =>
|
|
532
|
-
useWindowVirtualizer(() => ({
|
|
533
|
-
count: 1000,
|
|
534
|
-
estimateSize: () => 35,
|
|
535
|
-
overscan: 0,
|
|
536
|
-
})),
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
const { result: large, unmount: unmount2 } = mountWith(() =>
|
|
540
|
-
useWindowVirtualizer(() => ({
|
|
541
|
-
count: 1000,
|
|
542
|
-
estimateSize: () => 35,
|
|
543
|
-
overscan: 10,
|
|
544
|
-
})),
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
expect(large.virtualItems().length).toBeGreaterThanOrEqual(small.virtualItems().length)
|
|
548
|
-
unmount1()
|
|
549
|
-
unmount2()
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
it('onChange callback updates signals when triggered directly', () => {
|
|
553
|
-
const onChangeSpy = vi.fn()
|
|
554
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
555
|
-
useWindowVirtualizer(() => ({
|
|
556
|
-
count: 100,
|
|
557
|
-
estimateSize: () => 50,
|
|
558
|
-
onChange: onChangeSpy,
|
|
559
|
-
})),
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
// Trigger the constructor's onChange callback (lines 63-68)
|
|
563
|
-
const onChange = virt.instance.options.onChange
|
|
564
|
-
if (onChange) {
|
|
565
|
-
onChange(virt.instance, false)
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
569
|
-
expect(typeof virt.totalSize()).toBe('number')
|
|
570
|
-
expect(typeof virt.isScrolling()).toBe('boolean')
|
|
571
|
-
expect(onChangeSpy).toHaveBeenCalled()
|
|
572
|
-
unmount()
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
it('onChange works without user-provided onChange', () => {
|
|
576
|
-
const { result: virt, unmount } = mountWith(() =>
|
|
577
|
-
useWindowVirtualizer(() => ({
|
|
578
|
-
count: 100,
|
|
579
|
-
estimateSize: () => 50,
|
|
580
|
-
})),
|
|
581
|
-
)
|
|
582
|
-
|
|
583
|
-
const onChange = virt.instance.options.onChange
|
|
584
|
-
if (onChange) {
|
|
585
|
-
onChange(virt.instance, false)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
expect(virt.virtualItems()).toBeDefined()
|
|
589
|
-
unmount()
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
it('handles missing document/window gracefully', () => {
|
|
593
|
-
const origDoc = globalThis.document
|
|
594
|
-
const origWin = globalThis.window
|
|
595
|
-
try {
|
|
596
|
-
// @ts-expect-error — temporarily remove globals to exercise SSR fallback branches
|
|
597
|
-
delete globalThis.document
|
|
598
|
-
// @ts-expect-error
|
|
599
|
-
delete globalThis.window
|
|
600
|
-
|
|
601
|
-
const { totalSize } = useWindowVirtualizer(() => ({
|
|
602
|
-
count: 100,
|
|
603
|
-
estimateSize: () => 50,
|
|
604
|
-
}))
|
|
605
|
-
|
|
606
|
-
expect(totalSize()).toBeGreaterThanOrEqual(0)
|
|
607
|
-
} finally {
|
|
608
|
-
globalThis.document = origDoc
|
|
609
|
-
globalThis.window = origWin
|
|
610
|
-
}
|
|
611
|
-
})
|
|
612
|
-
})
|