Skip to content

Commit 286156b

Browse files
author
dasathyakuma
committed
few more minor fixes and tests
1 parent 2a32067 commit 286156b

File tree

5 files changed

+122
-1
lines changed

5 files changed

+122
-1
lines changed

packages/marko-virtual/src/tags/virtualizer/index.marko

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export interface Input {
7070
if (!entry) return
7171
const { v } = entry
7272
73+
// Guard mirrors onMount's notify: bail if the instance was destroyed
74+
// between the time onUpdate was called and when onChange fires async.
7375
const notify = () => {
76+
if (!getInstance(id)) return
7477
items = v.getVirtualItems()
7578
size = v.getTotalSize()
7679
}

packages/marko-virtual/src/tags/window-virtualizer/index.marko

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export interface Input {
6666
if (!entry) return
6767
const { v } = entry
6868
69+
// Guard mirrors onMount's notify: bail if the instance was destroyed
70+
// between the time onUpdate was called and when onChange fires async.
6971
const notify = () => {
72+
if (!getInstance(id)) return
7073
items = v.getVirtualItems()
7174
size = v.getTotalSize()
7275
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Fixture for testing reactive count changes via onUpdate.
2+
// The <virtualizer> tag must recalculate items when count changes —
3+
// this requires the explicit notify() call after _willUpdate() in onUpdate.
4+
// Without it, count changes silently have no effect.
5+
6+
export interface Input {
7+
initialCount?: number
8+
}
9+
10+
<let/count = (input.initialCount ?? 3)/>
11+
12+
<div/scrollEl style="height: 400px; width: 400px; overflow: auto">
13+
<button
14+
data-testid="increment-btn"
15+
onClick=() => { count = count + 1 }
16+
/>
17+
<virtualizer|{ virtualItems, totalSize }|
18+
count=count
19+
estimateSize=() => 50
20+
getScrollElement=scrollEl
21+
>
22+
<div
23+
data-testid="virtual-wrapper"
24+
style=`height: ${totalSize}px; position: relative`
25+
>
26+
<for|item| of=virtualItems>
27+
<div
28+
data-testid="virtual-item"
29+
data-index=item.index
30+
style=`position: absolute; height: ${item.size}px; transform: translateY(${item.start}px)`
31+
>
32+
Row ${item.index}
33+
</div>
34+
</for>
35+
</div>
36+
</virtualizer>
37+
</div>

packages/marko-virtual/tests/index.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,11 @@ describe('column virtualizer', () => {
216216
onChange,
217217
})
218218

219-
v._didMount()
219+
const cleanup = v._didMount()
220220
// After mount with a 400px-wide container, virtual items should exist
221221
const items = v.getVirtualItems()
222222
expect(Array.isArray(items)).toBe(true)
223+
cleanup()
223224
})
224225
})
225226

packages/marko-virtual/tests/tags.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import { afterEach, describe, expect, test } from "vitest"
2727
import { waitFor } from "@testing-library/dom"
2828
import VirtualizerFixture from "./fixtures/virtualizer-fixture.marko"
2929
import 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.
3334
const Virtualizer = VirtualizerFixture as any
3435
const 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

Comments
 (0)