mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-14 16:07:52 +00:00
Next/Previous Difference (toolbar arrows, F7 / Shift+F7) moved the selection without focusing the editor, and CodeMirror draws the caret only in a focused editor, so the caret at the reached difference was invisible. The diff and merge editors now focus the pane they navigate in and centre the difference; with synchronized scrolling, a follower pane's delayed layout scroll no longer pulls the navigation source back (also under reduced motion). Co-authored-by: HandsomeXi <42278397+HandsomeXi@users.noreply.github.com>
182 lines
8.8 KiB
TypeScript
182 lines
8.8 KiB
TypeScript
import type { EditorView } from "@codemirror/view";
|
|
import type { Page } from "@playwright/test";
|
|
import { expect, test } from "./fixtures";
|
|
|
|
async function openChain(page: Page) {
|
|
await page.evaluate(async () => {
|
|
const inputPath = "/src/features/vcs/diffInput.ts";
|
|
const { openDiff, withChain } = await import(inputPath);
|
|
localStorage.setItem("openidea.vcs.diffOptions", JSON.stringify({ mode: "split", collapse: false }));
|
|
const files = ["a.ts", "b.ts"].map((name) => ({
|
|
path: `/dev/acme-api/${name}`,
|
|
left: { type: "text", text: "same\nbefore\ntail\n", label: "HEAD" },
|
|
right: { type: "text", text: "same\nafter\ntail\n", label: "Working tree" },
|
|
}));
|
|
await openDiff(withChain(files, 0), { preview: true });
|
|
});
|
|
const toolbar = page.getByRole("toolbar", { name: "Diff", exact: true });
|
|
await expect(toolbar).toContainText("1 difference");
|
|
return toolbar;
|
|
}
|
|
|
|
test("maximizes a diff preview tab and restores its previous layout", async ({ workbench: page }) => {
|
|
const toolbar = await openChain(page);
|
|
const viewer = toolbar.locator("..");
|
|
const project = page.getByRole("tree", { name: "Project", exact: true });
|
|
await expect(project).toBeVisible();
|
|
const before = (await viewer.boundingBox())!;
|
|
await toolbar.getByRole("button", { name: "Maximize Editor", exact: true }).click();
|
|
await expect(project).toBeHidden();
|
|
await expect.poll(async () => (await viewer.boundingBox())!.width).toBeGreaterThan(before.width + 100);
|
|
await expect(toolbar).toContainText("1 difference");
|
|
await toolbar.getByRole("button", { name: "Restore Size", exact: true }).click();
|
|
await expect(project).toBeVisible();
|
|
await expect.poll(async () => Math.abs((await viewer.boundingBox())!.width - before.width)).toBeLessThan(2);
|
|
await expect(toolbar).toContainText("1 difference");
|
|
});
|
|
|
|
test("continues between changed files from toolbar buttons and the keymap", async ({ workbench: page }) => {
|
|
const toolbar = await openChain(page);
|
|
const selected = page.getByRole("tablist", { name: "Editor tabs" }).locator('[aria-selected="true"]');
|
|
await toolbar.getByRole("button", { name: "Next Difference", exact: true }).click();
|
|
await expect(toolbar).toContainText("1 of 1 differences");
|
|
await toolbar.getByRole("button", { name: "Next Difference", exact: true }).click();
|
|
await expect(page.getByRole("status").filter({ hasText: "Press again to go to the next file" })).toBeVisible();
|
|
await expect(selected).toContainText("a.ts");
|
|
await page.keyboard.press("F7");
|
|
await expect(selected).toContainText("b.ts");
|
|
await expect(toolbar).toContainText("1 of 1 differences");
|
|
await toolbar.getByRole("button", { name: "Previous Difference", exact: true }).click();
|
|
await expect(page.getByRole("status").filter({ hasText: "Press again to go to the previous file" })).toBeVisible();
|
|
await toolbar.getByRole("button", { name: "Previous Difference", exact: true }).click();
|
|
await expect(selected).toContainText("a.ts");
|
|
await expect(toolbar).toContainText("1 of 1 differences");
|
|
});
|
|
|
|
test("skips binary and unchanged siblings and restores after changing preview files", async ({ workbench: page }) => {
|
|
const toolbar = await openChain(page);
|
|
await page.evaluate(async () => {
|
|
const inputPath = "/src/features/vcs/diffInput.ts";
|
|
const { openDiff, withChain } = await import(inputPath);
|
|
const files = [
|
|
["a.ts", "after\n"],
|
|
["binary.dat", "\0binary"],
|
|
["unchanged.ts", "before\n"],
|
|
["z.ts", "after\n"],
|
|
].map(([name, text]) => ({
|
|
path: `/dev/acme-api/${name}`,
|
|
left: { type: "text", text: "before\n", label: "HEAD" },
|
|
right: { type: "text", text, label: "Working tree" },
|
|
}));
|
|
await openDiff(withChain(files, 0), { preview: true });
|
|
});
|
|
await expect(toolbar).toContainText("1 difference");
|
|
await page.evaluate(async () => {
|
|
const layoutPath = "/src/workbench/toolwindows/store.ts";
|
|
const { useToolWindowLayout, hideAllToolWindows } = await import(layoutPath);
|
|
useToolWindowLayout.setState({ maximized: "left" });
|
|
hideAllToolWindows();
|
|
});
|
|
await toolbar.locator("..").locator(".cm-content").last().focus();
|
|
const selected = page.getByRole("tablist", { name: "Editor tabs" }).locator('[aria-selected="true"]');
|
|
await page.keyboard.press("F7");
|
|
await page.keyboard.press("F7");
|
|
await expect(selected).toContainText("z.ts");
|
|
await expect(toolbar).toContainText("1 of 1 differences");
|
|
await page.keyboard.press("F7");
|
|
await expect(selected).toContainText("z.ts");
|
|
await page.keyboard.press("Shift+F7");
|
|
await page.keyboard.press("Shift+F7");
|
|
await expect(selected).toContainText("a.ts");
|
|
await toolbar.getByRole("button", { name: "Restore Size", exact: true }).click();
|
|
const maximized = await page.evaluate(async () => {
|
|
const layoutPath = "/src/workbench/toolwindows/store.ts";
|
|
return (await import(layoutPath)).useToolWindowLayout.getState().maximized;
|
|
});
|
|
expect(maximized).toBe("left");
|
|
await expect(toolbar).toBeHidden();
|
|
await expect(page.getByRole("tree", { name: "Project", exact: true })).toBeVisible();
|
|
});
|
|
|
|
for (const reducedMotion of ["reduce", "no-preference"] as const) {
|
|
test.describe(`caret navigation with reducedMotion=${reducedMotion}`, () => {
|
|
test.use({ reducedMotion });
|
|
for (const [mode, alignChanges, side] of [
|
|
["split", false, 0],
|
|
["split", false, 1],
|
|
["split", true, 0],
|
|
["split", true, 1],
|
|
["unified", false, 0],
|
|
] as const) {
|
|
test(`centres the visible caret during ${mode} navigation (aligned=${alignChanges}, pane=${side})`, async ({
|
|
workbench: page,
|
|
}) => {
|
|
await page.evaluate(
|
|
async ({ mode, alignChanges }) => {
|
|
const inputPath = "/src/features/vcs/diffInput.ts";
|
|
const { openDiff } = await import(inputPath);
|
|
localStorage.setItem(
|
|
"openidea.vcs.diffOptions",
|
|
JSON.stringify({ mode, alignChanges, collapse: false, syncScroll: true }),
|
|
);
|
|
const lines = Array.from({ length: 800 }, (_, i) => `const value${i} = ${i};`);
|
|
const right = [...lines];
|
|
for (const i of [100, 400, 700]) right[i] = `const changed${i} = ${i};`;
|
|
await openDiff({
|
|
path: "/dev/acme-api/navigation.ts",
|
|
left: { type: "text", text: lines.join("\n"), label: "Before" },
|
|
right: { type: "text", text: right.join("\n"), label: "After" },
|
|
});
|
|
},
|
|
{ mode, alignChanges },
|
|
);
|
|
const toolbar = page.getByRole("toolbar", { name: "Diff", exact: true });
|
|
await expect(toolbar).toContainText("3 differences");
|
|
const pane = toolbar.locator("..").locator(".cm-editor").nth(side);
|
|
await pane.locator(".cm-content").focus();
|
|
const check = async (line: number) => {
|
|
await expect(pane).toHaveClass(/cm-focused/);
|
|
await expect
|
|
.poll(() =>
|
|
pane.locator(".cm-content").evaluate((content) => {
|
|
const view = (content as HTMLElement & { cmTile: { view: EditorView } }).cmTile.view;
|
|
const head = view.state.selection.main.head;
|
|
const line = view.state.doc.lineAt(head);
|
|
const outer = content.closest<HTMLElement>(".cm-mergeView");
|
|
const scroller = outer && outer.scrollHeight > outer.clientHeight ? outer : view.scrollDOM;
|
|
const rect = scroller.getBoundingClientRect();
|
|
const caret = view.coordsAtPos(head);
|
|
const y = caret ? (caret.top + caret.bottom) / 2 : null;
|
|
return {
|
|
line: line.number,
|
|
column: head - line.from,
|
|
centred: y !== null && Math.abs(y - rect.top - scroller.clientHeight / 2) < 3,
|
|
cursor: getComputedStyle(view.dom.querySelector(".cm-cursor")!).display !== "none",
|
|
blinking: /cm-blink/.test(getComputedStyle(view.dom.querySelector(".cm-cursorLayer")!).animationName),
|
|
};
|
|
}),
|
|
)
|
|
.toEqual({ line, column: 0, centred: true, cursor: true, blinking: true });
|
|
if (mode === "split" && !alignChanges) {
|
|
const tops = await toolbar
|
|
.locator("..")
|
|
.locator(".cm-scroller")
|
|
.evaluateAll((els) => els.map((el) => el.scrollTop));
|
|
expect(Math.abs(tops[0]! - tops[1]!)).toBeLessThan(2);
|
|
}
|
|
};
|
|
await toolbar.getByRole("button", { name: "Next Difference", exact: true }).click();
|
|
await check(101);
|
|
await page.keyboard.press("F7");
|
|
await check(401);
|
|
await page.keyboard.press("F7");
|
|
await check(701);
|
|
await toolbar.getByRole("button", { name: "Previous Difference", exact: true }).click();
|
|
await check(401);
|
|
await page.keyboard.press("Shift+F7");
|
|
await check(101);
|
|
});
|
|
}
|
|
});
|
|
}
|