Merge remote-tracking branch 'origin/main' into fix/agent-chat-transcript

# Conflicts:
#	src/platform/locales/text/en.json
Цей коміт міститься в:
l0ng-ai committed 2026-11-07 10:46:10 +08:00
коміт 021e1165a7
22 files changed
+442 -75

No files matched your search

+90
Переглянути файл
@@ -0,0 +1,90 @@
import { expect, ipcCalls, stubDesktop, test } from "./fixtures";
test("opens a commit's branch menu and acts in its linked repository", async ({ workbench: page }) => {
await stubDesktop(page);
await page.evaluate(async () => {
const primary = "/dev/acme-api";
const linked = "/dev/acme-web";
const tip = "1".repeat(40);
const h = window.__ipc.handlers;
h.vcs_roots = () =>
[
{ root: primary, head: "main" },
{ root: linked, head: "release" },
].map((repo) => ({ ...repo, operation: null, headCommit: tip }));
h.vcs_branch_info = ({ repo }) => ({
head: repo === linked ? "release" : "main",
headCommit: tip,
upstream: null,
ahead: 0,
behind: 0,
operation: null,
});
h.vcs_branches = ({ repo }) =>
[repo === linked ? "origin/dev" : "origin/other"].map((name) => ({
name,
commit: tip,
remote: true,
current: false,
upstream: null,
ahead: 0,
behind: 0,
lastCommitMs: 0,
}));
h.vcs_log = ({ repo }) => {
const summary = repo === linked ? "Linked repository commit" : "Primary repository commit";
return [
{
id: tip,
shortId: tip.slice(0, 8),
parents: [],
refs: [repo === linked ? "origin/dev" : "origin/other"],
author: "A",
email: "[email protected]",
timeMs: 0,
summary,
message: summary,
},
];
};
h.vcs_log_refs = () => ({});
h.vcs_log_details_batch = ({ revisions }) =>
(revisions as string[]).map(() => ({
message: "Linked repository commit",
containingBranches: [],
signature: "N",
signer: "",
key: "",
fingerprint: "",
}));
h.vcs_commit_files = () => [];
h.vcs_config_get = () => null;
h.vcs_tags = () => [];
h.vcs_status = () => [];
h.vcs_create_branch = () => null;
const vcsModule = "/src/features/vcs/store.ts";
const { useVcs, refreshNow } = await import(/* @vite-ignore */ vcsModule);
useVcs.setState({ available: true, root: primary, repo: primary });
await refreshNow();
const contextModule = "/src/platform/context.ts";
const { setContext } = await import(/* @vite-ignore */ contextModule);
setContext("vcsAvailable", true);
const actionsModule = "/src/platform/actions.ts";
const { executeAction } = await import(/* @vite-ignore */ actionsModule);
await executeAction("vcs.showLog");
});
await page.getByRole("option", { name: /Linked repository commit/ }).click({ button: "right" });
const menu = page.getByRole("menu", { name: "Context menu" });
await expect(menu.getByRole("menuitem", { name: "Branch 'origin/other'" })).toHaveCount(0);
await menu.getByRole("menuitem", { name: "Branch 'origin/dev'" }).hover();
await expect(page.getByRole("menuitem", { name: "Pull into 'release' Using Rebase" })).toBeVisible();
const newBranch = page.getByRole("menuitem", { name: "New Branch from 'origin/dev'…" });
await newBranch.click();
const dialog = page.getByRole("dialog", { name: "Create New Branch from origin/dev" });
await dialog.getByRole("textbox", { name: "New branch name:" }).fill("test-branch");
await dialog.getByRole("button", { name: "Create", exact: true }).click();
await expect
.poll(() => ipcCalls(page, "vcs_create_branch"))
.toMatchObject([{ repo: "/dev/acme-web", name: "test-branch", start: "origin/dev", checkout: true }]);
});
+63 -23
Переглянути файл
@@ -7,30 +7,47 @@ import { expect, ipcCalls, stubDesktop, test } from "./fixtures";
const ROOT = "/dev/acme-api";
async function openNewBranchDialog(page: Page) {
async function openNewBranchDialog(page: Page, from?: string) {
await stubDesktop(page);
await page.evaluate(async (root) => {
const h = window.__ipc.handlers;
h.vcs_roots = () => [{ root, operation: null, head: "main", headCommit: "1111111" }];
h.vcs_branch_info = () => ({
head: "main",
headCommit: "1111111",
upstream: null,
ahead: 0,
behind: 0,
operation: null,
});
h.vcs_status = () => [];
h.vcs_create_branch = () => null;
const vcsModule = "/src/features/vcs/store.ts";
const { useVcs, refreshNow } = await import(/* @vite-ignore */ vcsModule);
useVcs.setState({ available: true, root, repo: root, branchesLoaded: false, stashesLoaded: false });
await refreshNow();
const actionsModule = "/src/platform/actions.ts";
const { executeAction } = await import(/* @vite-ignore */ actionsModule);
void executeAction("vcs.newBranch");
}, ROOT);
const dialog = page.getByRole("dialog", { name: "Create New Branch" });
await page.evaluate(
async ({ root, from }) => {
const h = window.__ipc.handlers;
h.vcs_roots = () => [{ root, operation: null, head: "main", headCommit: "1111111" }];
h.vcs_branch_info = () => ({
head: "main",
headCommit: "1111111",
upstream: null,
ahead: 0,
behind: 0,
operation: null,
});
h.vcs_status = () => [];
h.vcs_branches = () => [
{
name: "main",
current: true,
remote: false,
upstream: null,
ahead: 0,
behind: 0,
lastCommitMs: null,
commit: "1111111",
},
];
h.vcs_create_branch = () => null;
const vcsModule = "/src/features/vcs/store.ts";
const { useVcs, refreshNow, loadBranches } = await import(/* @vite-ignore */ vcsModule);
useVcs.setState({ available: true, root, repo: root, branchesLoaded: false, stashesLoaded: false });
await refreshNow();
await loadBranches();
const actionsModule = "/src/platform/actions.ts";
const { executeAction } = await import(/* @vite-ignore */ actionsModule);
if (from) void executeAction("vcs.log.newBranch", { id: from, summary: "Selected commit", repo: root });
else void executeAction("vcs.newBranch");
},
{ root: ROOT, from },
);
const dialog = page.getByRole("dialog", { name: from ? `Create New Branch from ${from}` : "Create New Branch" });
const input = dialog.getByRole("textbox", { name: "New branch name:" });
await expect(input).toBeVisible();
return { dialog, input };
@@ -48,6 +65,29 @@ test("a typed space becomes a separator and Create stays enabled", async ({ work
expect(call).toMatchObject({ name: "fix-login-redirect" });
});
test("overwrites a local branch only after explicitly opting in", async ({ workbench: page }) => {
const from = "2".repeat(40);
const { dialog, input } = await openNewBranchDialog(page, from);
await input.fill("main");
const create = dialog.getByRole("button", { name: "Create", exact: true });
await expect(create).toBeDisabled();
const overwrite = dialog.getByRole("checkbox", { name: "Overwrite existing branch" });
await expect(overwrite).toBeVisible();
await expect(overwrite).not.toBeChecked();
await overwrite.check();
await expect(create).toBeEnabled();
await input.fill("new-branch");
await expect(overwrite).toHaveCount(0);
await input.fill("main");
await expect(overwrite).not.toBeChecked();
await expect(create).toBeDisabled();
await overwrite.check();
await create.click();
await expect
.poll(() => ipcCalls(page, "vcs_create_branch"))
.toEqual([{ repo: ROOT, name: "main", start: from, checkout: true, overwrite: true }]);
});
test("a space typed in the middle keeps the caret after it", async ({ workbench: page }) => {
const { input } = await openNewBranchDialog(page);
await input.fill("ab");