diff --git a/apps/bench/src/__tests__/bench-runtime.test.ts b/apps/bench/src/__tests__/bench-runtime.test.ts
index c74de5df6..1f13762dc 100644
--- a/apps/bench/src/__tests__/bench-runtime.test.ts
+++ b/apps/bench/src/__tests__/bench-runtime.test.ts
@@ -1358,6 +1358,67 @@ describe("bench runtime", () => {
expect(result.metrics.interaction_latency_ms).toBeGreaterThanOrEqual(0);
});
+ test("measureBenchKeySequenceRun waits for cells that arrive after the viewport", async () => {
+ // The viewport element attaches before the row model projects its first
+ // window. One settle frame is not enough for that, so the run used to fail
+ // for want of a body cell that was about to exist — which is how all three
+ // selection scripts aborted the comparative runset at zero rendered rows.
+ document.body.innerHTML = `
+
+ `;
+ const root = document.querySelector('[data-testid="root"]')!;
+ const viewport = root.querySelector(
+ "[data-pretable-scroll-viewport]",
+ )!;
+ const dispatched: string[] = [];
+ const MOUNT_FRAME = 30;
+ let frame = 0;
+
+ const previousRaf = globalThis.requestAnimationFrame;
+ Object.defineProperty(globalThis, "requestAnimationFrame", {
+ configurable: true,
+ value: (callback: FrameRequestCallback) => {
+ frame += 1;
+ if (frame === MOUNT_FRAME) {
+ viewport.innerHTML = `
+
+ `;
+ viewport
+ .querySelector("[data-pretable-cell]")!
+ .addEventListener("keydown", (event) => {
+ dispatched.push((event as KeyboardEvent).key);
+ });
+ }
+ callback(frame * 16);
+ return frame;
+ },
+ });
+
+ try {
+ const result = await measureBenchKeySequenceRun(
+ root,
+ "pretable",
+ "keyboard-nav-row",
+ { key: "ArrowDown", shiftKey: false, count: 3, framesBetween: 1 },
+ );
+
+ expect(result.notes).not.toContain(
+ "no body cell available for keyboard focus",
+ );
+ expect(result.status).toBe("completed");
+ expect(dispatched).toEqual(["ArrowDown", "ArrowDown", "ArrowDown"]);
+ } finally {
+ Object.defineProperty(globalThis, "requestAnimationFrame", {
+ configurable: true,
+ value: previousRaf,
+ });
+ }
+ });
+
test("measureBenchKeySequenceRun returns partial when no viewport is present", async () => {
document.body.innerHTML = ``;
diff --git a/apps/bench/src/bench-runtime.ts b/apps/bench/src/bench-runtime.ts
index d0506f326..e5375cf30 100644
--- a/apps/bench/src/bench-runtime.ts
+++ b/apps/bench/src/bench-runtime.ts
@@ -1966,8 +1966,13 @@ export async function measureBenchKeySequenceRun(
};
}
- // Allow the grid to settle and ensure focus is on a body cell.
- await waitForAnimationFrame();
+ // Allow the grid to settle and ensure focus is on a body cell. One frame is
+ // not settling: the viewport attaches before the row model projects its first
+ // window, so a single frame leaves the body empty and the run fails below for
+ // want of a cell that is about to exist. This is the same wait `scroll` and
+ // `initial` take (#334); all three selection scripts aborted the comparative
+ // runset at zero rendered rows without it.
+ await waitForRenderedRowBaseline(root, profile.rowSelector);
const firstCell =
viewport.querySelector(
`${profile.cellSelector}[tabindex="0"]`,
@@ -2095,7 +2100,10 @@ export async function measureBenchAutosizeRun(
metrics: { dom_nodes_peak: root.querySelectorAll("*").length },
};
}
- await waitForAnimationFrame();
+ // Autosize measures the cost of fitting columns to their content, so the
+ // content has to be on screen first. One frame after mount it is not, and the
+ // run would time a fit over an empty body.
+ await waitForRenderedRowBaseline(root, profile.rowSelector);
const start = performance.now();
await autosize();
await waitForAnimationFrame();