diff --git a/runner/e2e/starter-matrix.spec.ts b/runner/e2e/starter-matrix.spec.ts index f700ebb3f..18b1e03f8 100644 --- a/runner/e2e/starter-matrix.spec.ts +++ b/runner/e2e/starter-matrix.spec.ts @@ -173,14 +173,51 @@ for (const entry of catalog.examples) { .toBeGreaterThan(0); if (entry.framework === "example1") { - // Read every cell and assert over the SET. A `filter({ hasText })` + // Assert over the SET of rendered cell text. A `filter({ hasText })` // locator would be a strict-mode violation: Handsontable renders the // same logical cell again in its clone_top/clone_left overlay tables, // which is why every other grid assertion here counts rather than // matches. - const rendered = new Set(await cells.allInnerTexts()); + // + // And read that set across the grid's whole scroll width, not just + // the initial viewport. The starter sizes to its container (DEV-2794 + // dropped `width: 800`), which in this suite's Desktop Chrome + // viewport is a 624px pane holding four of the 150px columns, so + // Item Quality sits past the rendered edge. Column virtualization + // then decides whether its exists at all: 17.1.0 and 18 render + // exactly one column beyond the viewport (`viewportColumnRenderingOffset: + // "auto"` is a fixed 1 since 17.1.0) and drop it; 15 and 16 still + // render enough to include it, by a hair. Sweeping the master holder + // makes the read independent of pane width and of which offset policy + // the major ships. + const holder = frame.locator(".ht_master .wtHolder"); + const renderedAcrossScrollWidth = async () => { + const seen = new Set(); + const { clientWidth, scrollWidth } = await holder.evaluate((el) => ({ + clientWidth: el.clientWidth, + scrollWidth: el.scrollWidth, + })); + for (let left = 0; left < scrollWidth; left += Math.max(clientWidth, 1)) { + // Walkontable re-renders inside its scroll handler, which the + // browser fires a frame after `scrollLeft` changes — so wait two + // frames before reading the DOM. `expect.poll` below re-sweeps if + // a render is slower than that, so this only tunes speed. + await holder.evaluate( + (el, x) => + new Promise((done) => { + el.scrollLeft = x; + requestAnimationFrame(() => requestAnimationFrame(() => done())); + }), + left, + ); + for (const text of await cells.allInnerTexts()) seen.add(text); + } + return [...seen]; + }; for (const expected of example1NumericCells(major)) { - expect(rendered.has(expected), `numericFormat rendered ${expected} at ${major}`).toBe(true); + await expect + .poll(renderedAcrossScrollWidth, { message: `numericFormat rendered ${expected} at ${major}` }) + .toContain(expected); } }