* perf(app): bound large diff row mounting Keep the existing vertical and horizontal scroll owners so virtualization does not change gesture or scrolling behavior. Reserve exact offscreen row heights while mounting only the visible window, and retain the full renderer when wrapping or editing reviews requires dynamic geometry. * perf(app): keep large diff reviews responsive Opening an inline editor changed the whole review action object, reconciled large syntax-token trees, and disabled row virtualization. Keep the expensive token subtree stable and preserve deterministic row geometry while the editor is open.\n\nBound server-side highlighting for pathological single lines and remove the unused client highlighter so the actual diff pipeline owns the limit. * test(app): check mounted diff typography Row virtualization can unmount a fixed diff row after scrolling. Assert the configured font size against a currently mounted row so the test continues to verify rendered typography without depending on window position. * test(app): stabilize Mermaid streaming coverage
185 lines
7.7 KiB
TypeScript
185 lines
7.7 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import type { CDPSession } from "@playwright/test";
|
|
import { buildHostWorkspaceRoute } from "../../src/utils/host-routes";
|
|
import { test, expect } from "../support/fixtures";
|
|
import { getServerId } from "../support/helpers/server-id";
|
|
import { connectSeedClient } from "../support/helpers/seed-client";
|
|
import { createTempGitRepo } from "../support/helpers/workspace";
|
|
import { waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
|
|
|
|
const RUN_DIFF_PERF = process.env.PASEO_DIFF_PERF_E2E === "1";
|
|
const diffPerfDescribe = RUN_DIFF_PERF ? test.describe : test.describe.skip;
|
|
const LINE_COUNT = 2_400;
|
|
const CPU_SLOWDOWN = Number(process.env.PASEO_DIFF_PERF_CPU_SLOWDOWN ?? 6);
|
|
const MAX_MOUNTED_CODE_ROWS = 220;
|
|
const MAX_EXPANSION_MS = 6_000;
|
|
const CHANGES_PREFERENCES_KEY = "@paseo:changes-preferences";
|
|
|
|
diffPerfDescribe("Diff rendering performance", () => {
|
|
test("large unwrapped diffs keep mounting bounded under CPU slowdown", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.setTimeout(90_000);
|
|
const repo = await createTempGitRepo("diff-perf-", {
|
|
files: [{ path: "src/large.ts", content: "export const baseline = 0;\n" }],
|
|
});
|
|
const client = await connectSeedClient();
|
|
let cdp: CDPSession | null = null;
|
|
|
|
try {
|
|
const generatedLines = Array.from(
|
|
{ length: LINE_COUNT },
|
|
(_, index) => `export const generated_${index} = "${index}-${"wide".repeat(16)}";`,
|
|
).join("\n");
|
|
await writeFile(
|
|
path.join(repo.path, "src/large.ts"),
|
|
`export const baseline = 1;\n${generatedLines}\n`,
|
|
);
|
|
const created = await client.createWorkspace({
|
|
source: { kind: "directory", path: repo.path },
|
|
});
|
|
if (!created.workspace) {
|
|
throw new Error(created.error ?? "Failed to create diff performance workspace");
|
|
}
|
|
|
|
await page.addInitScript(
|
|
({ preferencesKey }) => {
|
|
localStorage.setItem(
|
|
preferencesKey,
|
|
JSON.stringify({
|
|
layout: "unified",
|
|
viewMode: "flat",
|
|
wrapLines: false,
|
|
hideWhitespace: false,
|
|
}),
|
|
);
|
|
},
|
|
{ preferencesKey: CHANGES_PREFERENCES_KEY },
|
|
);
|
|
await page.setViewportSize({ width: 1400, height: 900 });
|
|
await page.goto(buildHostWorkspaceRoute(getServerId(), created.workspace.id));
|
|
await waitForWorkspaceTabsVisible(page);
|
|
await page.getByRole("button", { name: "Open explorer" }).click();
|
|
const fileHeader = page.getByTestId("diff-file-0");
|
|
await expect(fileHeader).toContainText("large.ts", { timeout: 30_000 });
|
|
|
|
cdp = await page.context().newCDPSession(page);
|
|
await cdp.send("Emulation.setCPUThrottlingRate", { rate: CPU_SLOWDOWN });
|
|
const expansionStart = await page.evaluate(() => performance.now());
|
|
await fileHeader.click();
|
|
await expect(page.getByTestId("diff-file-0-body")).toBeVisible({ timeout: 30_000 });
|
|
await expect(page.getByTestId("diff-code-row-0")).toBeVisible({ timeout: 30_000 });
|
|
const expansionMs = (await page.evaluate(() => performance.now())) - expansionStart;
|
|
|
|
const scroll = page.getByTestId("git-diff-scroll");
|
|
const initial = await readDiffMountReport(page);
|
|
expect(initial.mountedCodeRows).toBeLessThanOrEqual(MAX_MOUNTED_CODE_ROWS);
|
|
expect(initial.mountedGutterRows).toBe(initial.mountedCodeRows);
|
|
|
|
await page.getByTestId("diff-gutter-row-1").hover();
|
|
await page.getByTestId("diff-gutter-action-1").click();
|
|
await expect(page.getByTestId("inline-review-editor")).toBeVisible();
|
|
const withEditor = await readDiffMountReport(page);
|
|
expect(withEditor.mountedCodeRows).toBeLessThanOrEqual(MAX_MOUNTED_CODE_ROWS);
|
|
expect(withEditor.mountedGutterRows).toBe(withEditor.mountedCodeRows);
|
|
expect(await readDiffRowAlignment(page, 2)).toBeCloseTo(0, 0);
|
|
await expect(page.getByTestId("inline-review-editor-input")).toBeFocused();
|
|
await page.getByTestId("inline-review-editor-cancel").click();
|
|
await expect(page.getByTestId("inline-review-editor")).toHaveCount(0);
|
|
|
|
await scroll.evaluate((element) => {
|
|
element.scrollTop = element.scrollHeight - element.clientHeight;
|
|
element.dispatchEvent(new Event("scroll", { bubbles: false }));
|
|
});
|
|
await expect
|
|
.poll(async () => (await readDiffMountReport(page)).highestMountedRow)
|
|
.toBeGreaterThan(LINE_COUNT - 100);
|
|
const afterScroll = await readDiffMountReport(page);
|
|
expect(afterScroll.mountedCodeRows).toBeLessThanOrEqual(MAX_MOUNTED_CODE_ROWS);
|
|
expect(afterScroll.mountedGutterRows).toBe(afterScroll.mountedCodeRows);
|
|
|
|
const report = {
|
|
cpuSlowdown: CPU_SLOWDOWN,
|
|
sourceLines: LINE_COUNT,
|
|
expansionMs: Math.round(expansionMs),
|
|
initial,
|
|
withEditor,
|
|
afterScroll,
|
|
};
|
|
await testInfo.attach("diff-performance", {
|
|
body: JSON.stringify(report, null, 2),
|
|
contentType: "application/json",
|
|
});
|
|
console.log(`[perf] Diff rendering: ${JSON.stringify(report)}`);
|
|
expect(expansionMs).toBeLessThan(MAX_EXPANSION_MS);
|
|
|
|
await cdp.send("Emulation.setCPUThrottlingRate", { rate: 1 });
|
|
await scroll.evaluate((element) => {
|
|
element.scrollTop = 0;
|
|
element.dispatchEvent(new Event("scroll", { bubbles: false }));
|
|
});
|
|
await page.getByTestId("changes-toggle-layout").click();
|
|
const oldLine = page.getByText("export const baseline = 0;", { exact: true });
|
|
const newLine = page.getByText("export const baseline = 1;", { exact: true });
|
|
await expect(oldLine).toBeVisible();
|
|
await expect(newLine).toBeVisible();
|
|
const [bodyBox, oldBox, newBox] = await Promise.all([
|
|
page.getByTestId("diff-file-0-body").boundingBox(),
|
|
oldLine.boundingBox(),
|
|
newLine.boundingBox(),
|
|
]);
|
|
expect(bodyBox).not.toBeNull();
|
|
expect(oldBox).not.toBeNull();
|
|
expect(newBox).not.toBeNull();
|
|
expect(oldBox!.x).toBeLessThan(newBox!.x);
|
|
expect(oldBox!.x).toBeGreaterThanOrEqual(bodyBox!.x);
|
|
expect(newBox!.x).toBeGreaterThan(bodyBox!.x + bodyBox!.width / 2);
|
|
expect(newBox!.x).toBeLessThan(bodyBox!.x + bodyBox!.width);
|
|
} finally {
|
|
await cdp?.send("Emulation.setCPUThrottlingRate", { rate: 1 }).catch(() => undefined);
|
|
await cdp?.detach().catch(() => undefined);
|
|
await client.close().catch(() => undefined);
|
|
await repo.cleanup().catch(() => undefined);
|
|
}
|
|
});
|
|
});
|
|
|
|
async function readDiffRowAlignment(
|
|
page: import("@playwright/test").Page,
|
|
rowIndex: number,
|
|
): Promise<number> {
|
|
const gutterTop = await page
|
|
.getByTestId(`diff-gutter-row-${rowIndex}`)
|
|
.evaluate((row) => row.getBoundingClientRect().top);
|
|
const codeTop = await page
|
|
.getByTestId(`diff-code-row-${rowIndex}`)
|
|
.evaluate((row) => row.getBoundingClientRect().top);
|
|
return gutterTop - codeTop;
|
|
}
|
|
|
|
async function readDiffMountReport(page: import("@playwright/test").Page): Promise<{
|
|
mountedCodeRows: number;
|
|
mountedGutterRows: number;
|
|
highestMountedRow: number;
|
|
bodyDomElements: number;
|
|
}> {
|
|
return page.getByTestId("diff-file-0-body").evaluate((body) => {
|
|
const codeRows = Array.from(
|
|
body.querySelectorAll<HTMLElement>('[data-testid^="diff-code-row-"]'),
|
|
);
|
|
const gutterRows = body.querySelectorAll('[data-testid^="diff-gutter-row-"]');
|
|
return {
|
|
mountedCodeRows: codeRows.length,
|
|
mountedGutterRows: gutterRows.length,
|
|
highestMountedRow: Math.max(
|
|
-1,
|
|
...codeRows.map((row) =>
|
|
Number((row.getAttribute("data-testid") ?? "").slice("diff-code-row-".length)),
|
|
),
|
|
),
|
|
bodyDomElements: body.querySelectorAll("*").length,
|
|
};
|
|
});
|
|
}
|