mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-14 16:07:52 +00:00
The Name field was squeezed and the dialog overflowed in narrow or zoomed windows because the sidebar, label column and header controls had fixed widths. Add a remembered sidebar splitter, shared label columns, and wrapping Name/storage and footer groups; Before launch actions stay together and both panes keep scrolling independently when the dialog is resized.
199 lines
9.8 KiB
TypeScript
199 lines
9.8 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
import { expect, test } from "./fixtures";
|
|
|
|
async function openConfigurations(page: Page) {
|
|
await page.evaluate(async () => {
|
|
const path = "/src/features/run/store.ts";
|
|
const { useRun } = await import(/* @vite-ignore */ path);
|
|
const base = {
|
|
kind: "shell",
|
|
command: "/Users/developer/projects/acme/services/payments/scripts/start-development-server",
|
|
args: ["--watch"],
|
|
cwd: "/Users/developer/projects/acme/services/payments",
|
|
env: { NODE_ENV: "development" },
|
|
detected: false,
|
|
isTest: false,
|
|
debugAdapter: null,
|
|
};
|
|
useRun.setState({
|
|
configs: [
|
|
{
|
|
...base,
|
|
id: "layout:app",
|
|
name: "Payments development server",
|
|
beforeLaunch: [{ kind: "run", target: "layout:prepare" }],
|
|
},
|
|
{ ...base, id: "layout:prepare", name: "Prepare generated sources for the payments development server" },
|
|
],
|
|
selectedId: "layout:app",
|
|
loaded: true,
|
|
editorOpen: true,
|
|
});
|
|
});
|
|
await expect(page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true })).toBeVisible();
|
|
}
|
|
|
|
test("run configuration sidebar resizes with mouse and keyboard and remembers the layout", async ({
|
|
workbench: page,
|
|
}) => {
|
|
await openConfigurations(page);
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
const handle = dialog.getByRole("separator", { name: "Run configurations", exact: true });
|
|
await expect(handle).toBeVisible();
|
|
const sidebar = dialog.locator("#run-config-navigation");
|
|
const initial = (await sidebar.boundingBox())!.width;
|
|
const box = (await handle.boundingBox())!;
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 55, box.y + box.height / 2, { steps: 5 });
|
|
await page.mouse.up();
|
|
await expect.poll(async () => (await sidebar.boundingBox())!.width).toBeGreaterThan(initial + 35);
|
|
const resized = (await sidebar.boundingBox())!.width;
|
|
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
|
await openConfigurations(page);
|
|
await expect.poll(async () => (await sidebar.boundingBox())!.width).toBeCloseTo(resized, 0);
|
|
await handle.focus();
|
|
await page.keyboard.press("ArrowLeft");
|
|
await expect.poll(async () => (await sidebar.boundingBox())!.width).toBeLessThan(resized);
|
|
});
|
|
|
|
test("run configuration fields share a control column including the name", async ({ workbench: page }) => {
|
|
await openConfigurations(page);
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
const fields = ["Name", "Command", "Working directory"].map((name) =>
|
|
dialog.getByRole("textbox", { name, exact: true }),
|
|
);
|
|
for (const field of fields) await expect(field).toBeVisible();
|
|
const boxes = await Promise.all(fields.map((field) => field.boundingBox()));
|
|
for (const box of boxes) expect(box!.x).toBeCloseTo(boxes[0]!.x, 0);
|
|
expect(boxes[0]!.width).toBeGreaterThan(80);
|
|
});
|
|
|
|
test("resizing the configuration dialog grows its panes and keeps the footer visible", async ({ workbench: page }) => {
|
|
await openConfigurations(page);
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
const content = dialog.locator("#run-config-content");
|
|
const initial = (await content.boundingBox())!.height;
|
|
const grip = dialog.getByRole("separator", { name: "Resize", exact: true });
|
|
const box = (await grip.boundingBox())!;
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 80, box.y + 80, { steps: 5 });
|
|
await page.mouse.up();
|
|
await expect.poll(async () => (await content.boundingBox())!.height).toBeGreaterThan(initial + 50);
|
|
const grown = (await grip.boundingBox())!;
|
|
await page.mouse.move(grown.x + grown.width / 2, grown.y + grown.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(grown.x - 320, grown.y - 400, { steps: 5 });
|
|
await page.mouse.up();
|
|
await expect(dialog.getByRole("button", { name: "OK", exact: true })).toBeInViewport();
|
|
await expect(dialog.getByRole("tree", { name: "Run configurations", exact: true })).toBeVisible();
|
|
await expect.poll(async () => (await content.boundingBox())!.height).toBeLessThan(initial);
|
|
await expect
|
|
.poll(() => dialog.locator("[class*='editForm_']").evaluate((el) => el.scrollHeight - el.clientHeight))
|
|
.toBeGreaterThan(0);
|
|
});
|
|
|
|
test("configuration templates keep the form and sidebar usable", async ({ workbench: page }) => {
|
|
await openConfigurations(page);
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
await dialog.getByRole("button", { name: "Edit configuration templates…", exact: true }).click();
|
|
await dialog.getByRole("option", { name: "Shell Script", exact: true }).click();
|
|
await expect(dialog.getByRole("textbox", { name: "Script path", exact: true })).toBeVisible();
|
|
await expect(dialog.getByRole("separator", { name: "Run configurations", exact: true })).toBeVisible();
|
|
await dialog.getByRole("button", { name: "Back to configurations", exact: true }).click();
|
|
await expect(dialog.getByRole("textbox", { name: "Name", exact: true })).toHaveValue("Payments development server");
|
|
});
|
|
|
|
test("Java, npm and Python forms fit narrow and zoomed content panes", async ({ workbench: page }) => {
|
|
for (const kind of ["application", "npm", "pytest"]) {
|
|
await page.evaluate(async (kind) => {
|
|
const store = "/src/features/run/store.ts";
|
|
const types = "/src/features/run/types.ts";
|
|
const { useRun } = await import(/* @vite-ignore */ store);
|
|
const { newConfigOfType, typeFor } = await import(/* @vite-ignore */ types);
|
|
const config = newConfigOfType(typeFor(kind)!, []);
|
|
useRun.setState({ configs: [config], selectedId: config.id, loaded: true, editorOpen: true });
|
|
}, kind);
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
await expect(dialog).toBeVisible();
|
|
for (const zoom of [1, 1.25]) {
|
|
await page.evaluate((zoom) => document.documentElement.style.setProperty("zoom", String(zoom)), zoom);
|
|
for (const width of [1024, 640, 520]) {
|
|
await page.setViewportSize({ width, height: 820 });
|
|
await expect
|
|
.poll(() => dialog.locator("[class*='editForm_']").evaluate((el) => el.scrollWidth - el.clientWidth), {
|
|
message: `${kind}, ${width}px, ${zoom * 100}%`,
|
|
})
|
|
.toBeLessThanOrEqual(1);
|
|
}
|
|
}
|
|
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
|
}
|
|
});
|
|
|
|
test("Tomcat retains its tabbed form and an independently scrolling panel", async ({ workbench: page }) => {
|
|
await page.evaluate(async () => {
|
|
const store = "/src/features/run/store.ts";
|
|
const types = "/src/features/run/types.ts";
|
|
const { useRun } = await import(/* @vite-ignore */ store);
|
|
const { newConfigOfType, typeFor } = await import(/* @vite-ignore */ types);
|
|
const config = newConfigOfType(typeFor("tomcat")!, []);
|
|
useRun.setState({ configs: [config], selectedId: config.id, loaded: true, editorOpen: true });
|
|
});
|
|
const dialog = page.getByRole("dialog", { name: "Run/Debug Configurations", exact: true });
|
|
await expect(dialog).toBeVisible();
|
|
for (const tab of ["Server", "Deployment", "Logs", "Startup/Connection"]) {
|
|
await dialog.getByRole("tab", { name: tab, exact: true }).click();
|
|
const panel = dialog.getByRole("tabpanel");
|
|
await expect(panel).toBeVisible();
|
|
await expect.poll(async () => (await panel.boundingBox())!.height).toBeGreaterThan(100);
|
|
}
|
|
});
|
|
|
|
for (const locale of ["en", "zh"]) {
|
|
test(`${locale} run forms and footer fit narrow and zoomed windows`, async ({ workbench: page }) => {
|
|
await openConfigurations(page);
|
|
await page.getByRole("textbox", { name: "Name", exact: true }).fill("");
|
|
await page.evaluate(async (locale) => {
|
|
const path = "/src/platform/i18n.ts";
|
|
await (await import(/* @vite-ignore */ path)).setLocale(locale);
|
|
}, locale);
|
|
const dialog = page.getByRole("dialog");
|
|
for (const zoom of [1, 1.25]) {
|
|
await page.evaluate((zoom) => document.documentElement.style.setProperty("zoom", String(zoom)), zoom);
|
|
for (const width of [1024, 800, 640, 520]) {
|
|
await page.setViewportSize({ width, height: 820 });
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
dialog.evaluate((el) => {
|
|
const body = el.querySelector<HTMLElement>("[class*='editForm_']")!;
|
|
const footer = el.querySelector<HTMLElement>("[data-dialog-footer]")!;
|
|
const bounds = footer.getBoundingClientRect();
|
|
const buttons = [...footer.querySelectorAll("button")].map((b) => b.getBoundingClientRect());
|
|
return {
|
|
overflow: body.scrollWidth - body.clientWidth,
|
|
footerFits: buttons.every((b) => b.left >= bounds.left - 1 && b.right <= bounds.right + 1),
|
|
footerVisible: bounds.bottom <= window.innerHeight + 1,
|
|
};
|
|
}),
|
|
{ message: `${locale}, ${width}px, ${zoom * 100}%` },
|
|
)
|
|
.toMatchObject({ overflow: expect.any(Number), footerFits: true, footerVisible: true });
|
|
await expect
|
|
.poll(() => dialog.locator("[class*='editForm_']").evaluate((el) => el.scrollWidth - el.clientWidth))
|
|
.toBeLessThanOrEqual(1);
|
|
const tasks = dialog.getByRole("list");
|
|
await tasks.scrollIntoViewIfNeeded();
|
|
const bounds = (await tasks.boundingBox())!;
|
|
for (const button of await tasks.getByRole("button").all()) {
|
|
const box = (await button.boundingBox())!;
|
|
expect(box.x).toBeGreaterThanOrEqual(bounds.x - 1);
|
|
expect(box.x + box.width).toBeLessThanOrEqual(bounds.x + bounds.width + 1);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|