diff --git a/src/js/core/row/Row.js b/src/js/core/row/Row.js
index 946199c21..63393e9ad 100644
--- a/src/js/core/row/Row.js
+++ b/src/js/core/row/Row.js
@@ -113,6 +113,7 @@ export default class Row extends CoreFeature{
deinitialize(){
this.initialized = false;
+ this.heightInitialized = false;
}
deinitializeHeight(){
diff --git a/test/e2e/cell-height-recycle.html b/test/e2e/cell-height-recycle.html
new file mode 100644
index 000000000..e6ec202ef
--- /dev/null
+++ b/test/e2e/cell-height-recycle.html
@@ -0,0 +1,56 @@
+
+
+
+
+ Tabulator Virtual Cell Height Recycling Test
+
+
+
+
+
+
+
+
+
+
diff --git a/test/e2e/scroll-jump.spec.ts b/test/e2e/scroll-jump.spec.ts
index 9467f663f..3e1f565a7 100644
--- a/test/e2e/scroll-jump.spec.ts
+++ b/test/e2e/scroll-jump.spec.ts
@@ -200,6 +200,63 @@ test.describe("Vertical scroll jumping with variable height rows (#3654)", () =>
});
});
+test.describe("Cell heights after virtual row recycling", () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`file://${join(__dirname, "cell-height-recycle.html")}`);
+ await page.waitForSelector(".tabulator-row");
+ await page.locator(".tabulator-tableholder").hover();
+ });
+
+ test("normalizes cells recreated after an offscreen column refit", async ({
+ page,
+ }) => {
+ await scrollToBottom(page, 900);
+ await expect(
+ page
+ .locator('.tabulator-cell[tabulator-field="id"]')
+ .filter({ hasText: /^1$/ }),
+ ).toHaveCount(0);
+
+ // A column refit makes the horizontal virtual renderer discard cells for
+ // rows outside its current vertical window.
+ await page.evaluate(() => window.testTable.getColumn("name").setWidth(1200));
+
+ // A direct jump takes the vertical renderer's full-fill path when it
+ // recreates the discarded cells.
+ await page.locator(".tabulator-tableholder").evaluate((holder) => {
+ holder.scrollTop = 0;
+ holder.dispatchEvent(new Event("scroll"));
+ });
+
+ await expect
+ .poll(() =>
+ page
+ .locator(".tabulator-row")
+ .first()
+ .locator(".tabulator-cell")
+ .first()
+ .textContent(),
+ )
+ .toBe("1");
+
+ const geometry = await page.locator(".tabulator-row").first().evaluate((row) => ({
+ rowHeight: row.getBoundingClientRect().height,
+ cells: [...row.querySelectorAll(".tabulator-cell")].map((cell) => ({
+ field: cell.getAttribute("tabulator-field"),
+ height: cell.getBoundingClientRect().height,
+ styledHeight: Number.parseFloat((cell as HTMLElement).style.height),
+ })),
+ }));
+
+ expect(geometry.rowHeight).toBe(43);
+ expect(geometry.cells.length).toBeGreaterThan(0);
+ for (const cell of geometry.cells) {
+ expect(cell.height, cell.field).toBe(geometry.rowHeight);
+ expect(cell.styledHeight, cell.field).toBe(geometry.rowHeight);
+ }
+ });
+});
+
test.describe("Vertical scroll jumping with grouped variable height rows (#3654)", () => {
test.beforeEach(async ({ page }) => {
await page.goto(`file://${join(__dirname, "scroll-jump-group.html")}`);
diff --git a/test/unit/core/row/Row.spec.js b/test/unit/core/row/Row.spec.js
new file mode 100644
index 000000000..5c4c1ec6c
--- /dev/null
+++ b/test/unit/core/row/Row.spec.js
@@ -0,0 +1,14 @@
+import Row from "../../../../src/js/core/row/Row.js";
+
+describe("Row", () => {
+ test("deinitialize invalidates the cached row height", () => {
+ const row = Object.create(Row.prototype);
+ row.initialized = true;
+ row.heightInitialized = true;
+
+ row.deinitialize();
+
+ expect(row.initialized).toBe(false);
+ expect(row.heightInitialized).toBe(false);
+ });
+});