Skip to content

Commit 67f496f

Browse files
committed
test/docs: cover VirtualList onScroll
Add an integration test for wheel + key-driven scroll callbacks and document the onScroll range semantics.
1 parent 147acfc commit 67f496f

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

docs/widgets/virtual-list.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ ui.virtualList({
4242

4343
- Use `itemHeight` callback for variable-height rows.
4444
- `renderItem` receives a `focused` flag for styling.
45+
- The `range` passed to `onScroll` is `[startIndex, endIndex)` and includes overscan.
4546

4647
## Related
4748

packages/core/src/app/__tests__/widgetRenderer.integration.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ function keyEvent(key: number, mods = 0): ZrevEvent {
1818
return { kind: "key", timeMs: 0, key, mods, action: "down" };
1919
}
2020

21+
function mouseEvent(
22+
x: number,
23+
y: number,
24+
mouseKind: 1 | 2 | 3 | 4 | 5,
25+
opts: Readonly<{ wheelX?: number; wheelY?: number }> = {},
26+
): ZrevEvent {
27+
return {
28+
kind: "mouse",
29+
timeMs: 0,
30+
x,
31+
y,
32+
mouseKind,
33+
mods: 0,
34+
buttons: 0,
35+
wheelX: opts.wheelX ?? 0,
36+
wheelY: opts.wheelY ?? 0,
37+
};
38+
}
39+
2140
function createNoopBackend(): RuntimeBackend {
2241
return {
2342
start: async () => {},
@@ -243,6 +262,42 @@ describe("WidgetRenderer integration battery", () => {
243262
assert.deepEqual(selected, ["b:1"]);
244263
});
245264

265+
test("virtualList onScroll fires for wheel and key-driven scroll", () => {
266+
const backend = createNoopBackend();
267+
const renderer = new WidgetRenderer<void>({
268+
backend,
269+
requestRender: () => {},
270+
});
271+
272+
const scrolled: Array<Readonly<{ scrollTop: number; range: [number, number] }>> = [];
273+
274+
const vnode = ui.virtualList({
275+
id: "v",
276+
items: new Array<number>(100).fill(0).map((_, i) => i),
277+
itemHeight: 1,
278+
renderItem: (item) => ui.text(String(item)),
279+
onScroll: (scrollTop, range) => scrolled.push(Object.freeze({ scrollTop, range })),
280+
});
281+
282+
const res = renderer.submitFrame(
283+
() => vnode,
284+
undefined,
285+
{ cols: 20, rows: 10 },
286+
defaultTheme,
287+
noRenderHooks(),
288+
);
289+
assert.ok(res.ok);
290+
291+
renderer.routeEngineEvent(mouseEvent(1, 1, 5, { wheelY: 1 }));
292+
renderer.routeEngineEvent(keyEvent(3 /* TAB */));
293+
renderer.routeEngineEvent(keyEvent(13 /* END */));
294+
295+
assert.deepEqual(scrolled, [
296+
{ scrollTop: 3, range: [0, 16] },
297+
{ scrollTop: 90, range: [87, 100] },
298+
]);
299+
});
300+
246301
test("table routing moves focused row and activates on Enter", () => {
247302
const backend = createNoopBackend();
248303
const renderer = new WidgetRenderer<void>({

0 commit comments

Comments
 (0)