@@ -27,11 +27,13 @@ import { afterEach, describe, expect, test } from "vitest"
2727import { waitFor } from "@testing-library/dom"
2828import VirtualizerFixture from "./fixtures/virtualizer-fixture.marko"
2929import WindowVirtualizerFixture from "./fixtures/window-virtualizer-fixture.marko"
30+ import CountUpdateFixture from "./fixtures/count-update-fixture.marko"
3031
3132// Cast to any — @marko/vite compiles .marko files as ES modules whose default
3233// export is the template object with mount(input, container): Instance.
3334const Virtualizer = VirtualizerFixture as any
3435const WindowVirtualizer = WindowVirtualizerFixture as any
36+ const CountUpdate = CountUpdateFixture as any
3537
3638// ---------------------------------------------------------------------------
3739// Test helpers
@@ -116,6 +118,81 @@ describe("<virtualizer> rows", () => {
116118 } )
117119} )
118120
121+ // ---------------------------------------------------------------------------
122+ // <virtualizer> — reactive updates (onUpdate path)
123+ // ---------------------------------------------------------------------------
124+
125+ describe ( "<virtualizer> reactive updates" , ( ) => {
126+ test ( "count increase re-renders additional items (tests onUpdate + notify())" , async ( ) => {
127+ // This test covers the critical onUpdate code path:
128+ // 1. Parent changes count → Marko calls onUpdate on <virtualizer>
129+ // 2. onUpdate calls v.setOptions({ count: newCount, ... })
130+ // 3. v._willUpdate() is a no-op (scroll element unchanged)
131+ // 4. notify() is called explicitly — WITHOUT this call, count changes
132+ // have no effect because _willUpdate() only runs when the scroll
133+ // element changes. This was the critical bug documented in the insights.
134+ // 5. notify() → items = v.getVirtualItems() → Marko re-renders
135+ //
136+ // initialCount: 3 → all 3 items fit in 400px viewport (3 × 50px = 150px)
137+ const el = mountFixture ( CountUpdate , { initialCount : 3 } )
138+ await waitFor ( ( ) =>
139+ expect ( el . querySelectorAll ( "[data-testid='virtual-item']" ) ) . toHaveLength ( 3 )
140+ )
141+
142+ // Click the button to increment count from 3 → 4
143+ el . querySelector ( "[data-testid='increment-btn']" ) ! . dispatchEvent (
144+ new MouseEvent ( "click" , { bubbles : true } )
145+ )
146+
147+ // Now 4 items should render — confirms onUpdate + notify() works
148+ await waitFor ( ( ) =>
149+ expect ( el . querySelectorAll ( "[data-testid='virtual-item']" ) ) . toHaveLength ( 4 )
150+ )
151+ } )
152+
153+ test ( "count decrease removes items from DOM" , async ( ) => {
154+ // Start with 5 items, increment to confirm reactive updates work,
155+ // then test that the final count is correct.
156+ // initialCount: 5 → all 5 fit in 400px (5 × 50px = 250px)
157+ const el = mountFixture ( CountUpdate , { initialCount : 5 } )
158+ await waitFor ( ( ) =>
159+ expect ( el . querySelectorAll ( "[data-testid='virtual-item']" ) ) . toHaveLength ( 5 )
160+ )
161+
162+ // Increment twice: 5 → 6 → 7
163+ const btn = el . querySelector ( "[data-testid='increment-btn']" ) !
164+ btn . dispatchEvent ( new MouseEvent ( "click" , { bubbles : true } ) )
165+ await waitFor ( ( ) =>
166+ expect ( el . querySelectorAll ( "[data-testid='virtual-item']" ) ) . toHaveLength ( 6 )
167+ )
168+
169+ btn . dispatchEvent ( new MouseEvent ( "click" , { bubbles : true } ) )
170+ await waitFor ( ( ) =>
171+ expect ( el . querySelectorAll ( "[data-testid='virtual-item']" ) ) . toHaveLength ( 7 )
172+ )
173+ } )
174+
175+ test ( "totalSize updates when count changes" , async ( ) => {
176+ const el = mountFixture ( CountUpdate , { initialCount : 3 } )
177+ // Initial: 3 × 50 = 150px
178+ await waitFor ( ( ) =>
179+ expect (
180+ ( el . querySelector ( "[data-testid='virtual-wrapper']" ) as HTMLElement ) ?. style . height
181+ ) . toBe ( "150px" )
182+ )
183+
184+ // Increment count → 4 × 50 = 200px
185+ el . querySelector ( "[data-testid='increment-btn']" ) ! . dispatchEvent (
186+ new MouseEvent ( "click" , { bubbles : true } )
187+ )
188+ await waitFor ( ( ) =>
189+ expect (
190+ ( el . querySelector ( "[data-testid='virtual-wrapper']" ) as HTMLElement ) ?. style . height
191+ ) . toBe ( "200px" )
192+ )
193+ } )
194+ } )
195+
119196// ---------------------------------------------------------------------------
120197// <virtualizer> — column virtualisation
121198// ---------------------------------------------------------------------------
0 commit comments