Skip to content

Commit 72a60c2

Browse files
committed
test: lock overflow branch and scrollTop guard
- add test for overflow case where container is shorter than full content - new spec file covering the guard that prevents phantom page loads on content-resize
1 parent 0252366 commit 72a60c2

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

packages/pluggableWidgets/datagrid-web/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
- We added a "Custom row key" property in the Advanced section to provide stable row identifiers when using view entities, preventing scroll position loss during data refresh.
1212

13+
### Fixed
14+
15+
- We fixed an issue where the vertical scrollbar disappeared after hiding a wide column while virtual scrolling was enabled.
16+
- We fixed an issue where only the first page loaded when the grid had enough columns to require horizontal scrolling.
17+
1318
## [3.9.0] - 2026-03-23
1419

1520
### Changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { renderHook, act } from "@testing-library/react";
2+
import { createRef, UIEvent } from "react";
3+
4+
jest.mock("@mendix/widget-plugin-hooks/useOnScreen", () => ({
5+
useOnScreen: () => true
6+
}));
7+
8+
const mockBumpPage = jest.fn();
9+
const mockLockGridContainerHeight = jest.fn();
10+
const mockGridContainerRef = createRef<HTMLDivElement>();
11+
12+
jest.mock("../injection-hooks", () => ({
13+
useGridSizeStore: () => ({
14+
hasVirtualScrolling: true,
15+
gridContainerRef: mockGridContainerRef,
16+
bumpPage: mockBumpPage,
17+
lockGridContainerHeight: mockLockGridContainerHeight
18+
})
19+
}));
20+
21+
import { useInfiniteControl } from "../useInfiniteControl";
22+
import { VIRTUAL_SCROLLING_OFFSET } from "../../stores/GridSize.store";
23+
24+
function makeScrollEvent(overrides: {
25+
scrollTop: number;
26+
scrollHeight: number;
27+
clientHeight: number;
28+
scrollLeft?: number;
29+
}): UIEvent<HTMLDivElement> {
30+
const { scrollTop, scrollHeight, clientHeight, scrollLeft = 0 } = overrides;
31+
return {
32+
target: { scrollTop, scrollHeight, clientHeight, scrollLeft }
33+
} as unknown as UIEvent<HTMLDivElement>;
34+
}
35+
36+
describe("useInfiniteControl — trackTableScrolling", () => {
37+
beforeEach(() => {
38+
jest.useFakeTimers();
39+
mockBumpPage.mockClear();
40+
});
41+
42+
afterEach(() => {
43+
jest.useRealTimers();
44+
});
45+
46+
it("does not call bumpPage when scrollTop is 0, even if scroll position satisfies bottom condition", () => {
47+
const { result } = renderHook(() => useInfiniteControl());
48+
const [trackTableScrolling] = result.current;
49+
50+
// scrollTop === 0 → guard blocks bumpPage regardless of height math
51+
const e = makeScrollEvent({
52+
scrollTop: 0,
53+
scrollHeight: 500,
54+
clientHeight: 500
55+
});
56+
57+
act(() => {
58+
trackTableScrolling!(e);
59+
});
60+
61+
expect(mockBumpPage).not.toHaveBeenCalled();
62+
});
63+
64+
it("calls bumpPage when scrollTop > 0 and scroll position reaches the bottom", () => {
65+
const { result } = renderHook(() => useInfiniteControl());
66+
const [trackTableScrolling] = result.current;
67+
68+
// scrollTop > 0 and Math.floor(scrollHeight - OFFSET - scrollTop) <= clientHeight + 2
69+
const scrollHeight = 500;
70+
const clientHeight = 400;
71+
const scrollTop = scrollHeight - VIRTUAL_SCROLLING_OFFSET - clientHeight; // exactly at bottom
72+
73+
const e = makeScrollEvent({ scrollTop, scrollHeight, clientHeight });
74+
75+
act(() => {
76+
trackTableScrolling!(e);
77+
});
78+
79+
expect(mockBumpPage).toHaveBeenCalledTimes(1);
80+
});
81+
82+
it("does not call bumpPage when scrolled but not yet at the bottom", () => {
83+
const { result } = renderHook(() => useInfiniteControl());
84+
const [trackTableScrolling] = result.current;
85+
86+
const e = makeScrollEvent({
87+
scrollTop: 10,
88+
scrollHeight: 500,
89+
clientHeight: 100
90+
});
91+
92+
act(() => {
93+
trackTableScrolling!(e);
94+
});
95+
96+
expect(mockBumpPage).not.toHaveBeenCalled();
97+
});
98+
});

packages/pluggableWidgets/datagrid-web/src/model/stores/__tests__/GridSize.store.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ describe("GridSizeStore.lockGridContainerHeight()", () => {
173173
expect(store.gridContainerHeight).not.toBe(heightWithPageSize3);
174174
});
175175

176+
it("does not subtract offset when container height is smaller than full content height (overflow case)", () => {
177+
const { store } = buildStore({ pageSize: 3, columnCount: 2 });
178+
// fullHeight = 3×40 + 50 = 170px; container is only 100px → overflows
179+
setupRefs(store, { rowHeight: 40, rowCount: 3, headerHeight: 50, containerClientHeight: 100 });
180+
181+
store.lockGridContainerHeight();
182+
183+
expect(store.gridContainerHeight).toBe(170); // no offset subtracted
184+
});
185+
176186
it("clears and recomputes height when visible column count changes", () => {
177187
const { store, columnCountBox } = buildStore({ pageSize: 3, columnCount: 2 });
178188
setupRefs(store, { rowHeight: 40, rowCount: 3, headerHeight: 50, containerClientHeight: 1000 });

0 commit comments

Comments
 (0)