test(git): cover discard from nested workspaces through daemon RPC

This commit is contained in:
见安
2026-10-13 10:26:19 +08:00
parent 4948a96946
commit 3b36a20daf
@@ -1,4 +1,5 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest";
import { execFileSync } from "node:child_process";
import { mkdtempSync, writeFileSync, rmSync, mkdirSync, symlinkSync, readdirSync } from "fs";
import { tmpdir } from "os";
import path from "path";
@@ -22,6 +23,80 @@ afterEach(async () => {
await ctx.cleanup();
}, 60000);
describe("checkout discard", () => {
test.each([".", "apps/web"])(
"discards selected Changes from the %s workspace without changing siblings",
async (workspacePath) => {
const cwd = tmpCwd();
function git(args: string[]): string {
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: "pipe" });
}
try {
git(["init", "-b", "main"]);
git(["config", "user.name", "Paseo Test"]);
git(["config", "user.email", "test@getpaseo.local"]);
git(["config", "core.autocrlf", "false"]);
const nested = path.join(cwd, "apps", "web");
mkdirSync(nested, { recursive: true });
writeFileSync(path.join(nested, "target[ab].txt"), "target base\n");
writeFileSync(path.join(nested, "targeta.txt"), "sibling base\n");
writeFileSync(path.join(nested, "deleted.txt"), "deleted base\n");
git(["add", "."]);
git(["-c", "commit.gpgsign=false", "commit", "-m", "initial"]);
const creation = await ctx.client.createWorkspace({
source: { kind: "directory", path: path.join(cwd, workspacePath) },
title: "Discard regression",
});
const workspaceCwd = creation.workspace?.workspaceDirectory;
if (!workspaceCwd) {
throw new Error(creation.error ?? "Workspace creation failed");
}
writeFileSync(path.join(nested, "target[ab].txt"), "target edited\n");
writeFileSync(path.join(nested, "targeta.txt"), "sibling edited\n");
writeFileSync(path.join(nested, "staged.txt"), "staged new file\n");
writeFileSync(path.join(nested, "scratch.txt"), "untracked file\n");
git(["--literal-pathspecs", "add", "--", "apps/web/target[ab].txt", "apps/web/staged.txt"]);
git(["rm", "apps/web/deleted.txt"]);
const subscription = ctx.client.observeCheckoutDiff(workspaceCwd, { mode: "uncommitted" });
try {
const changes = await subscription.ready;
expect(changes.error).toBeNull();
expect(changes.files.map((file) => file.path).sort()).toEqual([
"apps/web/deleted.txt",
"apps/web/scratch.txt",
"apps/web/staged.txt",
"apps/web/target[ab].txt",
"apps/web/targeta.txt",
]);
const selectedPaths = changes.files
.filter((file) => file.path !== "apps/web/targeta.txt")
.map((file) => file.path);
const discarded = await ctx.client.checkoutDiscardChanges(workspaceCwd, {
paths: selectedPaths,
});
expect(discarded).toMatchObject({ success: true, error: null });
const target = await ctx.client.readFile(cwd, "apps/web/target[ab].txt");
expect(new TextDecoder().decode(target.bytes)).toBe("target base\n");
const restored = await ctx.client.readFile(cwd, "apps/web/deleted.txt");
expect(new TextDecoder().decode(restored.bytes)).toBe("deleted base\n");
const sibling = await ctx.client.readFile(cwd, "apps/web/targeta.txt");
expect(new TextDecoder().decode(sibling.bytes)).toBe("sibling edited\n");
await expect(ctx.client.readFile(cwd, "apps/web/staged.txt")).rejects.toThrow(/ENOENT/);
await expect(ctx.client.readFile(cwd, "apps/web/scratch.txt")).rejects.toThrow(/ENOENT/);
expect(git(["status", "--porcelain=v1", "-z"])).toBe(" M apps/web/targeta.txt\0");
} finally {
await subscription.release();
}
} finally {
rmSync(cwd, { recursive: true, force: true });
}
},
);
});
describe("file explorer", () => {
test.each(["..", "..."])(
"workspace suggestions resolve exact paths starting with %s",