Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource-variable/fraunces": "^5.2.9",
"@fontsource-variable/inter": "^5.2.8",
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@mui/material": "^5.18.0",
"@mui/x-data-grid": "^7.29.13",
"@pretable-internal/bench-runner": "workspace:*",
"@pretable-internal/scenario-data": "workspace:*",
"@pretable/react": "workspace:*",
Expand Down
36 changes: 36 additions & 0 deletions apps/bench/src/__tests__/mui-adapter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, waitFor } from "@testing-library/react";
import { describe, expect, test } from "vitest";

import { MuiAdapter } from "../mui-adapter";

const dataset = {
columns: [
{ id: "id", header: "ID", wrap: false, widthPx: 80 },
{ id: "name", header: "Name", wrap: false, widthPx: 160 },
],
rows: [
{ id: "1", name: "Alpha" },
{ id: "2", name: "Beta" },
],
};

describe("MuiAdapter", () => {
test("mounts and renders MUI DataGrid public selectors", async () => {
const { container } = render(
<MuiAdapter dataset={dataset as never} runKey={0} />,
);

// Asserts on .MuiDataGrid-virtualScroller — the same selector the
// bench-runtime profile uses as the viewport — to catch class-name
// drift on minor MUI bumps. If a future MUI release stops mounting
// the virtual scroller in jsdom (no real layout), fall back to
// .MuiDataGrid-root and document the limitation. As of @mui/x-data-grid@7
// the scroller node is present even without layout.
await waitFor(() => {
expect(
container.querySelector(".MuiDataGrid-virtualScroller"),
).not.toBeNull();
expect(container.querySelector(".MuiDataGrid-root")).not.toBeNull();
});
});
});
110 changes: 102 additions & 8 deletions apps/bench/src/mui-adapter.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,120 @@
import type { ScenarioDataset } from "@pretable-internal/scenario-data";
import { useEffect, useMemo, useRef, useState } from "react";
import { DataGrid, type GridColDef } from "@mui/x-data-grid";

import type {
ScenarioColumn,
ScenarioDataset,
ScenarioRow,
} from "@pretable-internal/scenario-data";

import type { ApplyBenchUpdates } from "./bench-runtime";

const VIEWPORT_HEIGHT = 320;
const ROW_HEIGHT = 48;

export interface MuiAdapterProps {
dataset: ScenarioDataset;
onUpdateApiReady?: (apply: ApplyBenchUpdates) => void;
runKey: number;
scriptName?: string;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars -- placeholder; props consumed in Phase 3
export function MuiAdapter(_props: MuiAdapterProps) {
function toColDef(
column: ScenarioColumn,
scriptName: string | undefined,
): GridColDef {
const def: GridColDef = {
field: column.id,
headerName: column.header ?? column.id,
width: column.widthPx ?? 140,
sortable: true,
filterable: true,
resizable: true,
};

if (scriptName === "scroll-with-format") {
def.valueFormatter = (value: unknown) =>
Array.isArray(value) ? value.join(", ") : String(value ?? "");
} else if (scriptName === "scroll-with-render") {
def.renderCell = (params) => (
<span data-bench-render="cheap">{String(params.value ?? "")}</span>
);
} else if (scriptName === "scroll-with-heavy-render") {
def.renderCell = (params) => (
<span data-bench-render="heavy" className="bench-status-badge">
<span className="bench-badge-dot" aria-hidden />
<span>{String(params.value ?? "")}</span>
</span>
);
}

return def;
}

export function MuiAdapter({
dataset,
onUpdateApiReady,
runKey,
scriptName,
}: MuiAdapterProps) {
const onUpdateApiReadyRef = useRef(onUpdateApiReady);
// eslint-disable-next-line react-hooks/refs -- sync to latest
onUpdateApiReadyRef.current = onUpdateApiReady;

const [rows, setRows] = useState<ScenarioRow[]>(() => dataset.rows.slice());

useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect -- runKey reset
setRows(dataset.rows.slice());
}, [dataset.rows, runKey]);

const columns = useMemo(
() => dataset.columns.map((c) => toColDef(c, scriptName)),
[dataset.columns, scriptName],
);

useEffect(() => {
const apply: ApplyBenchUpdates = (patches) => {
setRows((prev) => {
const map = new Map(prev.map((r) => [String(r.id), r] as const));
for (const patch of patches) {
const id = String(patch.id);
const existing = map.get(id);
if (existing) {
map.set(id, { ...existing, ...patch } as ScenarioRow);
}
}
return Array.from(map.values());
});
};
onUpdateApiReadyRef.current?.(apply);
// Re-publish only on runKey change; bench-app keeps onUpdateApiReady
// stable via useCallback, and the ref above always reads the latest.
}, [runKey]);

return (
<section
aria-label="MUI X DataGrid adapter"
data-benchmark-adapter="mui"
style={{ padding: 16 }}
data-bench-result-row-count={String(rows.length)}
style={{ display: "grid", gap: 12 }}
>
<p style={{ margin: 0, fontWeight: 700 }}>MUI X DataGrid Community</p>
<p style={{ margin: "4px 0 0", opacity: 0.8 }}>
Real adapter ships in Phase 3 of B2. Currently a placeholder.
</p>
<header>
<p style={{ margin: 0, fontWeight: 700 }}>MUI X DataGrid Community</p>
<p style={{ margin: "4px 0 0", opacity: 0.8 }}>
Rows: {rows.length} · Columns: {dataset.columns.length}
</p>
</header>
<div key={runKey} style={{ height: VIEWPORT_HEIGHT, minWidth: 720 }}>
<DataGrid
rows={rows}
columns={columns}
rowHeight={ROW_HEIGHT}
hideFooter
disableRowSelectionOnClick
getRowId={(row) => String(row.id)}
/>
</div>
</section>
);
}
Loading