Skip to content

Commit 84a3463

Browse files
authored
feat(bench): make group-expand comparative against TanStack (closes #478) (#526)
* docs: plan comparator-generic group-expand (#478) * feat(bench): per-profile group-row selector (#478) * feat(bench): the group-expand plan names its collapse target (#478) * feat(bench): tanstack grouping setup + collapse handle for group-expand (#478) * feat(bench): pretable collapse handle for group-expand (#478) * feat(bench): comparator-generic group-expand setup and trigger (#478) * feat(bench): group-expand reads comparatively against tanstack (#478) * style: prettier
1 parent 9e7e9e0 commit 84a3463

17 files changed

Lines changed: 1014 additions & 111 deletions

‎apps/bench/src/__tests__/ag-grid-adapter.test.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ function filterPlan(
237237
): BenchInteractionPlan {
238238
return {
239239
focusedRowId: null,
240+
collapsedGroupKey: null,
241+
collapsedGroupRowCount: 0,
240242
filters,
241243
mode,
242244
probeColumnId: Object.keys(filters)[0] ?? "",

‎apps/bench/src/__tests__/bench-app-interaction-plan.test.tsx‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
ScenarioDataset,
66
ScenarioRow,
77
} from "@pretable-internal/scenario-data";
8+
import { createScenarioDataset } from "@pretable-internal/scenario-data";
89

910
const pretableAdapterSpy = vi.hoisted(() => vi.fn());
1011

@@ -187,6 +188,35 @@ const keystrokeRows = (values: string[]) =>
187188
(value, index) => ({ id: `row-${index}`, col_0: value }) as ScenarioRow,
188189
);
189190

191+
describe("the group-expand plan's collapse target", () => {
192+
test("names the sorted-first group and its row count", () => {
193+
const dataset = createScenarioDataset("S2", { scale: "dev" });
194+
const plan = createBenchInteractionPlan(dataset, "group-expand");
195+
expect(plan).not.toBeNull();
196+
// Sorted-first key of the grouping column, computed independently of the
197+
// builder so this test can disagree with it.
198+
const groupColumnId = plan!.rowGroups[0]!;
199+
const keys = [
200+
...new Set(dataset.rows.map((row) => String(row[groupColumnId] ?? ""))),
201+
].sort();
202+
expect(plan!.collapsedGroupKey).toBe(keys[0]);
203+
expect(plan!.collapsedGroupRowCount).toBe(
204+
dataset.rows.filter((row) => String(row[groupColumnId] ?? "") === keys[0])
205+
.length,
206+
);
207+
// Data that can disprove: an empty collapsed group would make the
208+
// toggle measure nothing.
209+
expect(plan!.collapsedGroupRowCount).toBeGreaterThan(0);
210+
});
211+
212+
test("every other mode carries no collapse target", () => {
213+
const dataset = createScenarioDataset("S2", { scale: "dev" });
214+
const groupPlan = createBenchInteractionPlan(dataset, "group");
215+
expect(groupPlan!.collapsedGroupKey).toBeNull();
216+
expect(groupPlan!.collapsedGroupRowCount).toBe(0);
217+
});
218+
});
219+
190220
describe("createBenchFilterKeystrokePlans", () => {
191221
// Fixture graded against the FULL needle "Bonjour depuis Pretable token-123"
192222
// (#509). Counts by hand (case-insensitive contains): "B":6 (all but

‎apps/bench/src/__tests__/bench-app.test.tsx‎

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
screen,
66
waitFor,
77
} from "@testing-library/react";
8-
import { afterEach, describe, expect, test, vi } from "vitest";
8+
import { afterEach, describe, expect, onTestFinished, test, vi } from "vitest";
99

1010
import { BENCH_RESULT_KEY } from "../bench-runtime";
1111
import * as benchRuntime from "../bench-runtime";
@@ -525,6 +525,121 @@ describe("BenchApp", () => {
525525
expect(groupRowsAtCallTime).toBeGreaterThan(0);
526526
}, 20_000);
527527

528+
test("groups the TANSTACK grid BEFORE the group-expand measurement window opens", async () => {
529+
// The comparator twin of the pretable test above (#478): the setup wait
530+
// is DOM-only and reads the per-profile groupRowSelector, so the same
531+
// invariant must hold with tanstack's painted rows.
532+
//
533+
// jsdom reports zero offsetWidth/offsetHeight and ships no
534+
// ResizeObserver, so @tanstack/react-virtual collapses to an empty
535+
// viewport and paints nothing — the same shim
536+
// tanstack-adapter.test.tsx installs, here scoped to this test so the
537+
// pretable paint tests keep jsdom's real (zero) layout.
538+
if (!("ResizeObserver" in globalThis)) {
539+
class StubResizeObserver {
540+
observe() {}
541+
unobserve() {}
542+
disconnect() {}
543+
}
544+
(
545+
globalThis as unknown as { ResizeObserver: typeof StubResizeObserver }
546+
).ResizeObserver = StubResizeObserver;
547+
}
548+
const baseOffsetWidth = Object.getOwnPropertyDescriptor(
549+
HTMLElement.prototype,
550+
"offsetWidth",
551+
);
552+
const baseOffsetHeight = Object.getOwnPropertyDescriptor(
553+
HTMLElement.prototype,
554+
"offsetHeight",
555+
);
556+
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
557+
configurable: true,
558+
get(this: HTMLElement) {
559+
if (this.hasAttribute?.("data-pretable-bench-tanstack-viewport"))
560+
return 720;
561+
return (baseOffsetWidth?.get?.call(this) as number | undefined) ?? 0;
562+
},
563+
});
564+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
565+
configurable: true,
566+
get(this: HTMLElement) {
567+
if (this.hasAttribute?.("data-pretable-bench-tanstack-viewport"))
568+
return 320;
569+
return (baseOffsetHeight?.get?.call(this) as number | undefined) ?? 0;
570+
},
571+
});
572+
onTestFinished(() => {
573+
if (baseOffsetWidth) {
574+
Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
575+
...baseOffsetWidth,
576+
configurable: true,
577+
});
578+
}
579+
if (baseOffsetHeight) {
580+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
581+
...baseOffsetHeight,
582+
configurable: true,
583+
});
584+
}
585+
});
586+
587+
let groupRowsAtCallTime = -1;
588+
let modeAtCallTime: string | null = null;
589+
590+
vi.spyOn(benchRuntime, "measureBenchInteractionRun").mockImplementation(
591+
async (_root, _adapterId, mode) => {
592+
modeAtCallTime = mode;
593+
groupRowsAtCallTime = document.querySelectorAll(
594+
"[data-tanstack-group-row]",
595+
).length;
596+
597+
return {
598+
status: "completed",
599+
notes: [`interaction mode: ${mode}`],
600+
metrics: {
601+
interaction_latency_ms: 9,
602+
settle_duration_ms: 8,
603+
post_interaction_blank_gap_frames: 0,
604+
post_interaction_anchor_shift_px: 0,
605+
post_interaction_row_height_error_p95_px: 0,
606+
post_interaction_row_height_error_measurable_rows: 11,
607+
result_row_count: 40,
608+
selected_row_preserved: 1,
609+
focused_row_preserved: 1,
610+
dom_nodes_peak: 400,
611+
rendered_rows_peak: 11,
612+
rendered_cells_peak: 440,
613+
},
614+
};
615+
},
616+
);
617+
618+
render(
619+
<BenchApp
620+
search="?adapter=tanstack&scenario=S2&scale=smoke&script=group-expand&autorun=1"
621+
browserVersion="123.0"
622+
/>,
623+
);
624+
625+
await waitFor(
626+
() => {
627+
expect(window[BENCH_RESULT_KEY]).toMatchObject({
628+
status: "completed",
629+
adapterId: "tanstack",
630+
scenarioId: "S2",
631+
scriptName: "group-expand",
632+
});
633+
},
634+
{ timeout: 15_000 },
635+
);
636+
637+
expect(modeAtCallTime).toBe("group-expand");
638+
// Row virtualization means only the group rows inside the viewport are in
639+
// the DOM, so this is "at least one", not "all".
640+
expect(groupRowsAtCallTime).toBeGreaterThan(0);
641+
}, 20_000);
642+
528643
test("declines to measure group-expand when the grouping never paints", async () => {
529644
// The CI failure this pins: the row MODEL settles several frames before
530645
// React commits the paint, so gating the wait on the model alone opened the

‎apps/bench/src/__tests__/bench-runtime.test.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
waitForRenderedRowBaseline,
2323
readBenchGridInstanceId,
2424
readRenderedFontStack,
25+
scrollRuntimeProfiles,
2526
UNREADABLE_FONT_STACK,
2627
} from "../bench-runtime";
2728
import { benchUpdatesExcludedColumnIds } from "../interaction-plan";
@@ -2146,6 +2147,27 @@ function installFrameStub(pending: {
21462147
};
21472148
}
21482149

2150+
describe("groupRowSelector", () => {
2151+
test("the group-capable adapters declare the selector their renderer paints", () => {
2152+
// These literals are the coupling #483 made load-bearing: the selector
2153+
// decides completed-vs-partial for the grouping scripts. A profile whose
2154+
// selector doesn't match what its renderer paints produces NO numbers
2155+
// (partial, every run), which reads as flake rather than a broken
2156+
// harness — so pin the exact strings.
2157+
expect(scrollRuntimeProfiles.pretable.groupRowSelector).toBe(
2158+
"[data-pretable-group-row]",
2159+
);
2160+
expect(scrollRuntimeProfiles.tanstack.groupRowSelector).toBe(
2161+
"[data-tanstack-group-row]",
2162+
);
2163+
});
2164+
2165+
test("the tier-excluded adapters declare none", () => {
2166+
expect(scrollRuntimeProfiles["ag-grid"].groupRowSelector).toBeUndefined();
2167+
expect(scrollRuntimeProfiles.mui.groupRowSelector).toBeUndefined();
2168+
});
2169+
});
2170+
21492171
describe("readBenchGridInstanceId", () => {
21502172
function createProbeRoot(markup: string) {
21512173
document.body.innerHTML = `<div data-testid="root">${markup}</div>`;

‎apps/bench/src/__tests__/mui-adapter.test.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ function filterPlan(
4949
): BenchInteractionPlan {
5050
return {
5151
focusedRowId: null,
52+
collapsedGroupKey: null,
53+
collapsedGroupRowCount: 0,
5254
filters,
5355
mode,
5456
probeColumnId: Object.keys(filters)[0] ?? "",

‎apps/bench/src/__tests__/pretable-adapter.test.tsx‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,55 @@ describe("PretableAdapter", () => {
135135
surfaceSpy.mockRestore();
136136
});
137137

138+
test("the collapse handle collapses exactly the group the plan names", async () => {
139+
const dataset = createScenarioDataset("S2", { scale: "smoke" });
140+
const plan = createBenchInteractionPlan(dataset, "group-expand")!;
141+
expect(plan.collapsedGroupKey).not.toBeNull();
142+
expect(plan.collapsedGroupRowCount).toBeGreaterThan(0);
143+
144+
const surfaceSpy = vi.spyOn(pretableReactInternal, "PretableSurface");
145+
let collapse: ((groupKey: string) => void) | null = null;
146+
147+
render(
148+
<PretableAdapter
149+
dataset={dataset}
150+
runKey={1}
151+
scriptName="group-expand"
152+
interactionPlan={plan}
153+
onGroupToggleReady={(fn) => {
154+
collapse = fn;
155+
}}
156+
/>,
157+
);
158+
159+
const model = (surfaceSpy.mock.calls.at(-1)?.[0] as unknown as SurfaceProps)
160+
.model;
161+
expect(model).toBeTruthy();
162+
expect(collapse).not.toBeNull();
163+
164+
// The grouped SETUP settles cooperatively post-#321 — poll the snapshot,
165+
// never assert synchronously.
166+
const groupedRowCount =
167+
dataset.rows.length +
168+
new Set(dataset.rows.map((row) => String(row[plan.rowGroups[0]!] ?? "")))
169+
.size;
170+
await waitFor(() => {
171+
expect(model!.getState().snapshot.visibleRowCount).toBe(groupedRowCount);
172+
});
173+
174+
act(() => collapse!(plan.collapsedGroupKey!));
175+
176+
// Every group row survives; the collapsed group's data rows do not — a
177+
// wrong-group collapse changes this delta.
178+
await waitFor(() => {
179+
expect(model!.getState().snapshot.visibleRowCount).toBe(
180+
groupedRowCount - plan.collapsedGroupRowCount,
181+
);
182+
});
183+
184+
surfaceSpy.mockRestore();
185+
});
186+
138187
test("installs the internal diagnostics controller only for explicit diagnostic runs and removes it on unmount", async () => {
139188
const dataset = createScenarioDataset("S5", { scale: "smoke" });
140189
const { unmount } = render(

0 commit comments

Comments
 (0)