Files
openidea/e2e/configurationNavigation.spec.ts
qiaojianlong c4b87ae09e perf(java): prewarm configuration navigation and share results between hover and click (#947)
Configuration navigation from YAML/properties still waited for a cold index the first
time and repeated the lookup between the ⌘-hover probe and the click.

- Trusted projects with a Maven/Gradle/Eclipse build file start Java shortly after
  opening. Once JDT reports ServiceReady, the app calls the new
  openidea.java.configuration.warmup command: a low-priority background job builds
  the configuration index one project at a time (open YAML/properties modules first),
  waits for workspace builds, has a 30 s budget per project, and only fills free cache
  room.
- Index builds take a cancellable per-project lock instead of one global lock, so a
  background build never delays another project, and a warmed snapshot is dropped
  if the Java model changed meanwhile.
- In YAML/properties the editor underlines the whole key (hyphens and dots
  included), hover and Go to Definition share one bounded request per key, and the
  declaration chooser names the whole key.

Bundle java-usages 1.6.0 is rebuilt reproducibly with build.py against JDT LS
1.63.0-202610191322 (SHA-256 33cfa1bb…). The real-JDT harness checks that the first
lookup reuses the warmed index.

Co-authored-by: qjlqyf <109515705+qjlqyf@users.noreply.github.com>
2026-11-06 12:23:39 +08:00

92 lines
3.9 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { test, expect, type Page } from "@playwright/test";
async function keyPosition(page: Page) {
const line = page.locator("#config .cm-line").filter({ hasText: "driver-class-name" });
const box = (await line.boundingBox())!;
// Skip the two-space indentation, land within the actual key.
return { x: box.x + 45, y: box.y + box.height / 2 };
}
async function open(page: Page) {
await page.addInitScript(() => Object.defineProperty(Navigator.prototype, "platform", { get: () => "MacIntel" }));
await page.goto("/e2e/configurationNavigation.html");
await expect(page.locator("#config .cm-content")).toContainText("driver-class-name");
}
async function metrics(page: Page) {
return page.evaluate(() =>
(
window as unknown as {
navigationMetrics: () => {
requests: number;
hoverRequests: number;
latency: number[];
injectedServerDelayMs: number;
};
}
).navigationMetrics(),
);
}
test("modifier hover and repeated clicks share results; editing Java invalidates them", async ({ page }, info) => {
const errors: string[] = [];
page.on("pageerror", (e) => errors.push(e.message));
await open(page);
const key = await keyPosition(page);
await page.keyboard.down("Meta");
await page.mouse.move(key.x, key.y);
await expect(page.locator(".cm-lsp-link")).toHaveText("driver-class-name");
await expect(page.locator(".cm-lsp-link")).toHaveCSS("cursor", "pointer");
await expect(page.locator(".cm-lsp-link")).toHaveCSS("color", "rgb(33, 92, 200)");
expect((await metrics(page)).hoverRequests).toBe(0);
await expect(page.locator(".cm-lsp-nav-info")).toHaveCount(0);
await page.screenshot({ path: info.outputPath("configuration-link.png") });
await page.mouse.click(key.x, key.y);
await expect(page.locator("#opened")).toHaveText("/fixture/Settings.java:0");
await page.keyboard.up("Meta");
for (let i = 0; i < 10; i++) {
await page.getByRole("button", { name: "Back to configuration" }).click();
await page.keyboard.down("Meta");
await page.mouse.click(key.x, key.y);
await expect(page.locator("#java")).toBeVisible();
await page.keyboard.up("Meta");
}
const warm = await metrics(page);
expect(warm.requests).toBe(1);
expect(warm.latency).toHaveLength(11);
const evidence = info.outputPath("warm-navigation.json");
await writeFile(evidence, JSON.stringify(warm, null, 2));
await info.attach("warm-navigation.json", { path: evidence, contentType: "application/json" });
await page.getByRole("button", { name: "Edit Java target" }).click();
await page.getByRole("button", { name: "Back to configuration" }).click();
await page.keyboard.down("Meta");
await page.mouse.click(key.x, key.y);
await expect(page.locator("#opened")).toHaveText("/fixture/Settings.java:1");
expect((await metrics(page)).requests).toBe(2);
expect(errors).toEqual([]);
});
test("clicking before the hover query completes shares the in-flight request", async ({ page }, info) => {
await open(page);
const key = await keyPosition(page);
await page.keyboard.down("Meta");
await page.mouse.move(key.x, key.y);
await expect(page.locator("#requests")).toHaveText("1");
await page.mouse.click(key.x, key.y);
await expect(page.locator("#opened")).toHaveText("/fixture/Settings.java:0");
const result = await metrics(page);
expect(result.requests).toBe(1);
await info.attach("inflight-navigation.json", {
body: JSON.stringify(result, null, 2),
contentType: "application/json",
});
});
test("ordinary hover still shows configuration documentation", async ({ page }) => {
await open(page);
const key = await keyPosition(page);
await page.mouse.move(key.x, key.y);
await expect(page.locator(".cm-lsp-hover")).toContainText("dingtalk.driver-class-name String");
expect((await metrics(page)).requests).toBe(0);
expect((await metrics(page)).hoverRequests).toBeGreaterThan(0);
});