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); + }); +});