From 398ff4b2a4072631a756fe6cebe8dac3fa183ddf Mon Sep 17 00:00:00 2001 From: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:58:06 -0600 Subject: [PATCH 1/3] docs(react-virtual): explain element measurements in unit tests --- docs/framework/react/react-virtual.md | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/framework/react/react-virtual.md b/docs/framework/react/react-virtual.md index 02d544e05..5eb9a1c0a 100644 --- a/docs/framework/react/react-virtual.md +++ b/docs/framework/react/react-virtual.md @@ -145,3 +145,46 @@ const virtualizer = useVirtualizer({ directDomUpdatesMode: 'position', // Use top/left instead of transform }) ``` + +## Testing + +DOM environments such as jsdom and happy-dom do not perform layout. Setting a height in CSS is not enough for the virtualizer to find visible items, so `getVirtualItems()` can return an empty array in unit tests. + +For an element-based list, mock the scroll element's `clientHeight` and `clientWidth`. If your list uses `measureElement`, also mock the items' `offsetHeight` and `offsetWidth` for their initial measurements. Mocking only `getBoundingClientRect` or only `offsetHeight` on the scroll element does not provide its viewport size. + +For example, this Vitest setup gives a scroll element with `data-testid="scroller"` a 200 × 320 viewport and its items a height of 40: + +```tsx +import { afterEach, beforeEach, vi } from 'vitest' + +beforeEach(() => { + vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockImplementation( + function (this: HTMLElement) { + return this.dataset.testid === 'scroller' ? 200 : 0 + }, + ) + vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockImplementation( + function (this: HTMLElement) { + return this.dataset.testid === 'scroller' ? 320 : 0 + }, + ) + vi.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockImplementation( + function (this: HTMLElement) { + return this.hasAttribute('data-index') ? 40 : 0 + }, + ) + vi.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockImplementation( + function (this: HTMLElement) { + return this.hasAttribute('data-index') ? 320 : 0 + }, + ) +}) + +afterEach(() => { + vi.restoreAllMocks() +}) +``` + +Scope the mocks to your list so unrelated elements keep their default measurements. Restore the getters after each test to avoid affecting other tests. If your test environment does not provide `ResizeObserver`, supply a mock or polyfill as well. To test later item resizes, deliver `ResizeObserver` entries with the corresponding `borderBoxSize`. + +Alternatively, pass a custom [`observeElementRect`](../../api/virtualizer.md#observeelementrect) to report a viewport size without mocking its DOM getters. For dynamically measured items, you can also pass a custom [`measureElement`](../../api/virtualizer.md#measureelement). Use browser tests when you need real layout, scrolling, or resize behavior. From 678362c9699872a8d6d78491a028f69184cea979 Mon Sep 17 00:00:00 2001 From: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:29:03 -0600 Subject: [PATCH 2/3] docs: scope item measurements in testing example --- docs/framework/react/react-virtual.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/framework/react/react-virtual.md b/docs/framework/react/react-virtual.md index 5eb9a1c0a..0e302fb0c 100644 --- a/docs/framework/react/react-virtual.md +++ b/docs/framework/react/react-virtual.md @@ -170,12 +170,12 @@ beforeEach(() => { ) vi.spyOn(HTMLElement.prototype, 'offsetHeight', 'get').mockImplementation( function (this: HTMLElement) { - return this.hasAttribute('data-index') ? 40 : 0 + return this.matches('[data-testid="scroller"] [data-index]') ? 40 : 0 }, ) vi.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockImplementation( function (this: HTMLElement) { - return this.hasAttribute('data-index') ? 320 : 0 + return this.matches('[data-testid="scroller"] [data-index]') ? 320 : 0 }, ) }) @@ -185,6 +185,6 @@ afterEach(() => { }) ``` -Scope the mocks to your list so unrelated elements keep their default measurements. Restore the getters after each test to avoid affecting other tests. If your test environment does not provide `ResizeObserver`, supply a mock or polyfill as well. To test later item resizes, deliver `ResizeObserver` entries with the corresponding `borderBoxSize`. +Scope the mocks to your list so unrelated elements keep their default measurements. The item selectors use the default `data-index` attribute. If you set a custom `indexAttribute`, replace `data-index` with that attribute. Restore the getters after each test to avoid affecting other tests. If your test environment does not provide `ResizeObserver`, supply a mock or polyfill as well. To test later item resizes, deliver `ResizeObserver` entries with the corresponding `borderBoxSize`. Alternatively, pass a custom [`observeElementRect`](../../api/virtualizer.md#observeelementrect) to report a viewport size without mocking its DOM getters. For dynamically measured items, you can also pass a custom [`measureElement`](../../api/virtualizer.md#measureelement). Use browser tests when you need real layout, scrolling, or resize behavior. From 4c64baf6e0cf0e01e631477ae522ec453bcbbe28 Mon Sep 17 00:00:00 2001 From: adityareddy-dev <82123567+adityareddy-dev@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:57:39 -0600 Subject: [PATCH 3/3] docs: correct default item measurement description --- docs/api/virtualizer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/virtualizer.md b/docs/api/virtualizer.md index a5715fbc1..183adf65f 100644 --- a/docs/api/virtualizer.md +++ b/docs/api/virtualizer.md @@ -556,7 +556,7 @@ Measures the element using your configured `measureElement` virtualizer option. >... ``` -By default the `measureElement` virtualizer option is configured to measure elements with `getBoundingClientRect()`. +By default, `measureElement` uses `ResizeObserverEntry.borderBoxSize` when available. Without an observer entry, it reuses the cached size if one exists. Otherwise, it reads `offsetWidth` for horizontal lists or `offsetHeight` for vertical lists. With `useCachedMeasurements` enabled, it uses the cached size or `estimateSize` without measuring the DOM. ### `resizeItem`