Файли
openidea/e2e/settings.spec.ts
T
arookieofc f72e3a9a8a fix(fonts): prefer installed JetBrains Mono/Inter over bundled variable webfonts (#2311)
* fix(fonts): prefer installed JetBrains Mono/Inter over bundled variable webfonts

When the user has JetBrains Mono or Inter installed, the system outline
fonts carry TrueType hinting bytecode that the bundled fontsource variable
webfonts lack, so the installed face renders crisper at editor sizes and
matches IntelliJ IDEA. Keep the variable faces as fallbacks so machines
without a system install keep rendering (#26).

Fixes #2305

* test(e2e): expect installed Inter before bundled Inter Variable (#2305)

* test(e2e): match computed-style serialization in the Inter font assertion (#2305)

* chore: trigger CI (no runs created for 950cb4b4)
2026-11-09 19:43:48 +08:00

139 рядки
7.4 KiB
TypeScript

import { expect, test } from "./fixtures";
test("Add Python Interpreter keeps environment labels and fields inside their rows", async ({ workbench: page }) => {
await page.keyboard.press("Meta+,");
const settings = page.getByRole("dialog", { name: "Settings", exact: true });
await settings.getByRole("textbox", { name: "Search settings" }).fill("Python Interpreter");
await settings
.getByRole("tree", { name: "Settings pages" })
.getByRole("treeitem", { name: "Python Interpreter", exact: true })
.click();
await settings.getByRole("button", { name: "Add Interpreter…", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "Add Python Interpreter", exact: true });
await expect(dialog).toBeVisible();
for (const width of [1000, 800, 520]) {
await page.setViewportSize({ width, height: 760 });
await expect(async () => {
const labelsFit = await dialog.getByRole("list", { name: "Interpreter type" }).evaluate((list) =>
[...list.querySelectorAll("button")].every((button) => {
const box = button.getBoundingClientRect();
const range = document.createRange();
range.selectNodeContents(button);
return [...range.getClientRects()].every(
(r) => r.top >= box.top - 1 && r.bottom <= box.bottom + 1 && r.right <= box.right + 1,
);
}),
);
expect(labelsFit).toBe(true);
}).toPass({ timeout: 3000 });
const location = dialog.getByRole("textbox", { name: "Location:", exact: true });
const fieldSpacing = await location.evaluate((input) => ({
rowHeight: input.parentElement!.getBoundingClientRect().height,
fieldHeight: input.getBoundingClientRect().height,
}));
expect(fieldSpacing.rowHeight).toBeLessThanOrEqual(fieldSpacing.fieldHeight + 1);
const base = await dialog.getByRole("button", { name: "Base interpreter", exact: true }).boundingBox();
const loc = await location.boundingBox();
expect(base!.y - loc!.y - loc!.height).toBeLessThan(50);
}
});
test("Project Structure keeps edits across repeated shortcuts and applies them", async ({ workbench: page }) => {
await page.keyboard.press("Meta+;");
const dialog = page.getByRole("dialog", { name: "Project Structure", exact: true });
await expect(dialog).toBeVisible();
await dialog.getByLabel("Compiler output", { exact: true }).fill("out/classes");
await page.keyboard.press("Meta+;");
await expect(dialog.getByLabel("Compiler output", { exact: true })).toHaveValue("out/classes");
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
await expect(dialog.getByRole("button", { name: "Apply", exact: true })).toBeDisabled();
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
await page.keyboard.press("Meta+;");
await expect(dialog.getByLabel("Compiler output", { exact: true })).toHaveValue("out/classes");
await dialog.getByRole("button", { name: "Modules", exact: true }).click();
await dialog.getByRole("tab", { name: "Dependencies", exact: true }).click();
await expect(dialog.getByRole("button", { name: "Add Library", exact: true })).toBeDisabled();
await dialog.getByRole("button", { name: "Artifacts", exact: true }).click();
await expect(dialog.getByRole("button", { name: "JAR", exact: true })).toBeDisabled();
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
});
test("⌘, opens Settings with the page tree; Escape closes", async ({ workbench: page }) => {
await page.keyboard.press("Meta+,");
const dialog = page.getByRole("dialog", { name: "Settings" });
await expect(dialog).toBeVisible();
const item = (name: string) => dialog.getByRole("treeitem", { name, exact: true });
for (const name of ["Appearance & Behavior", "Keymap", "Editor"]) {
await expect(item(name)).toBeVisible();
}
// The tree is virtualized: collapse Editor (expanded by default) so the later groups render.
await item("Editor").click();
await page.keyboard.press("ArrowLeft");
await expect(item("Editor")).not.toHaveAttribute("aria-expanded", "true");
for (const name of ["Plugins", "Version Control"]) {
await expect(item(name)).toBeVisible();
}
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
});
test("Language Servers page installs a tool with progress (in-memory installer)", async ({ workbench: page }) => {
await page.keyboard.press("Meta+,");
const dialog = page.getByRole("dialog", { name: "Settings" });
// The page tree is virtualized; search brings the page into view.
await dialog.getByRole("textbox", { name: "Search settings" }).fill("Language Servers");
await dialog.getByRole("treeitem", { name: "Language Servers", exact: true }).click();
const list = dialog.getByRole("list", { name: /language servers/i });
await expect(list).toBeVisible();
await list.locator('[data-tool="gopls"]').click();
const details = dialog.getByLabel("gopls details");
await expect(details).toContainText("Missing");
await details.getByRole("button", { name: "Install gopls" }).click();
await expect(details.getByRole("progressbar", { name: "Installing gopls" })).toBeVisible();
await expect(details).toContainText("Installed");
await expect(details).toContainText("1.0.0");
await expect(details.getByRole("button", { name: "Uninstall" })).toBeVisible();
// Tools without an installer explain how to get them.
await list.locator('[data-tool="solargraph"]').click();
await expect(dialog.getByLabel("solargraph details")).toContainText("gem install solargraph");
});
test("font picker supports manual families and CJK fallback in browser development", async ({ workbench: page }) => {
await page.keyboard.press("Meta+,");
const dialog = page.getByRole("dialog", { name: "Settings" });
await dialog.getByRole("textbox", { name: "Search settings" }).fill("Typography Settings");
await dialog.getByRole("treeitem", { name: "Font", exact: true }).click();
const family = dialog.getByRole("combobox", { name: "Font", exact: true });
await family.click();
await expect(page.getByRole("option", { name: /JetBrains Mono/ })).toBeVisible();
await family.fill("Maple Mono NF");
await family.press("Enter");
await expect(family).toHaveValue("Maple Mono NF");
// The shipped face is Inter Variable, not a system-installed family named Inter.
const interLoaded = await page.evaluate(async () => {
const faces = await document.fonts.load('14px "Inter Variable"');
return faces.some((face) => face.family.includes("Inter Variable") && face.status === "loaded");
});
expect(interLoaded).toBe(true);
await family.fill("Inter");
await family.press("Enter");
// Computed styles drop redundant quotes around single-token families ("Inter" -> Inter).
await expect(dialog.getByLabel("Font preview")).toHaveCSS("font-family", /^Inter, "Inter Variable",/);
const fallback = dialog.getByRole("combobox", { name: "Fallback font", exact: true });
await family.fill("Maple Mono NF");
await family.press("Enter");
await fallback.fill("Inter");
await fallback.press("Enter");
await expect(dialog.getByLabel("Font preview")).toHaveCSS("font-family", /Maple Mono NF.*Inter Variable/);
await fallback.fill("思源宋体");
await fallback.press("Enter");
await expect(dialog.getByLabel("Font preview")).toHaveCSS("font-family", /思源宋体/);
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
await expect(dialog).toBeHidden();
});