mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 01:14:52 +00:00
* fix(server): preserve comparison base when restoring worktrees Restore archived worktrees from the workspace's saved comparison base instead of using the feature branch as its own base. Preserve the branch HEAD and allow restore when an exact comparison ref no longer exists. * test(app): isolate draft preference checks from menu animation
155 lines
6.5 KiB
TypeScript
155 lines
6.5 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { existsSync, writeFileSync } from "node:fs";
|
|
import { execFileSync } from "node:child_process";
|
|
import { join } from "node:path";
|
|
import {
|
|
restoreWorkspaceFromHistory,
|
|
expectCommittedFileAfterReload,
|
|
} from "../support/helpers/workspace-recovery";
|
|
import { expect, type Page } from "@playwright/test";
|
|
import { metroTest as test } from "../support/fixtures";
|
|
import { gotoAppShell } from "../support/helpers/app";
|
|
import {
|
|
createMockIdleAgent,
|
|
expectSessionRowArchived,
|
|
openSessions,
|
|
} from "../support/helpers/archive-tab";
|
|
import { buildCreateAgentPreferences, buildSeededHost } from "../support/helpers/daemon-registry";
|
|
import {
|
|
startIsolatedHostDaemon,
|
|
type IsolatedHostDaemon,
|
|
} from "../support/helpers/isolated-host-daemon";
|
|
import {
|
|
archiveWorkspaceFromDaemon,
|
|
connectNewWorkspaceDaemonClient,
|
|
createWorktreeViaDaemon,
|
|
openProjectViaDaemon,
|
|
} from "../support/helpers/new-workspace";
|
|
import { connectSeedClient } from "../support/helpers/seed-client";
|
|
import { createTempGitRepo } from "../support/helpers/workspace";
|
|
import { waitForSidebarHydration } from "../support/helpers/workspace-ui";
|
|
|
|
test.describe("Worktree restore after daemon restart", () => {
|
|
const serverId = `srv_worktree_restart_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
let daemon: IsolatedHostDaemon;
|
|
let client: Awaited<ReturnType<typeof connectSeedClient>>;
|
|
let worktreeClient: Awaited<ReturnType<typeof connectNewWorkspaceDaemonClient>>;
|
|
let tempRepo: { path: string; cleanup: () => Promise<void> };
|
|
const createdWorktreeDirectories = new Set<string>();
|
|
const createdProjectIds = new Set<string>();
|
|
|
|
test.describe.configure({ retries: 0, timeout: 180_000 });
|
|
|
|
test.beforeEach(async () => {
|
|
daemon = await startIsolatedHostDaemon(serverId);
|
|
client = await connectSeedClient({ port: daemon.port });
|
|
worktreeClient = await connectNewWorkspaceDaemonClient({
|
|
port: daemon.port,
|
|
ownProjects: false,
|
|
});
|
|
tempRepo = await createTempGitRepo("wt-restart-");
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
for (const directory of createdWorktreeDirectories) {
|
|
await archiveWorkspaceFromDaemon(worktreeClient, directory).catch(() => undefined);
|
|
}
|
|
createdWorktreeDirectories.clear();
|
|
for (const projectId of createdProjectIds) {
|
|
await worktreeClient.removeProject(projectId).catch(() => undefined);
|
|
}
|
|
createdProjectIds.clear();
|
|
await client?.close().catch(() => undefined);
|
|
await worktreeClient?.close().catch(() => undefined);
|
|
await tempRepo?.cleanup().catch(() => undefined);
|
|
await daemon?.close().catch(() => undefined);
|
|
});
|
|
|
|
async function seedBrowser(page: Page) {
|
|
const nowIso = new Date().toISOString();
|
|
await page.addInitScript(
|
|
({ host, preferences }) => {
|
|
localStorage.setItem("@paseo:e2e", "1");
|
|
localStorage.setItem("@paseo:daemon-registry", JSON.stringify([host]));
|
|
localStorage.removeItem("@paseo:settings");
|
|
localStorage.setItem("@paseo:create-agent-preferences", JSON.stringify(preferences));
|
|
},
|
|
{
|
|
host: buildSeededHost({
|
|
serverId,
|
|
endpoint: `127.0.0.1:${daemon.port}`,
|
|
label: "restart daemon",
|
|
nowIso,
|
|
}),
|
|
preferences: buildCreateAgentPreferences(),
|
|
},
|
|
);
|
|
}
|
|
|
|
test("after archiving a worktree and restarting the daemon, History shows the worktree branch (not main) and restores its committed changes", async ({
|
|
page,
|
|
}) => {
|
|
// A paseo worktree is cut on its own branch named after the slug, and the
|
|
// worktree workspace is displayed under the same name. These are the values
|
|
// the History table cells must show after restore — never "main".
|
|
const worktreeSlug = `restart-restore-${randomUUID().slice(0, 8)}`;
|
|
|
|
const project = await openProjectViaDaemon(worktreeClient, tempRepo.path);
|
|
createdProjectIds.add(project.projectKey);
|
|
const worktree = await createWorktreeViaDaemon(worktreeClient, {
|
|
cwd: tempRepo.path,
|
|
slug: worktreeSlug,
|
|
});
|
|
createdProjectIds.add(worktree.projectKey);
|
|
createdWorktreeDirectories.add(worktree.workspaceDirectory);
|
|
|
|
writeFileSync(join(worktree.workspaceDirectory, "restart-change.txt"), "persisted base\n");
|
|
execFileSync("git", ["add", "restart-change.txt"], { cwd: worktree.workspaceDirectory });
|
|
execFileSync("git", ["commit", "-m", "Survive restart"], { cwd: worktree.workspaceDirectory });
|
|
|
|
const agent = await createMockIdleAgent(client, {
|
|
cwd: worktree.workspaceDirectory,
|
|
workspaceId: worktree.workspaceId,
|
|
title: `restart-restore-${randomUUID().slice(0, 8)}`,
|
|
});
|
|
expect(existsSync(worktree.workspaceDirectory)).toBe(true);
|
|
|
|
// Archive through the default production path (no scope): the worktree dir is deleted.
|
|
await archiveWorkspaceFromDaemon(worktreeClient, worktree.workspaceDirectory);
|
|
await expect
|
|
.poll(() => existsSync(worktree.workspaceDirectory), { timeout: 30_000 })
|
|
.toBe(false);
|
|
|
|
// Restart this spec's daemon on the same home and port so it rebuilds all
|
|
// workspace/agent links from persisted state without replacing the shared
|
|
// Playwright daemon owned by global setup.
|
|
await client.close().catch(() => undefined);
|
|
await worktreeClient.close().catch(() => undefined);
|
|
await daemon.restart();
|
|
client = await connectSeedClient({ port: daemon.port });
|
|
worktreeClient = await connectNewWorkspaceDaemonClient({
|
|
port: daemon.port,
|
|
ownProjects: false,
|
|
});
|
|
|
|
await seedBrowser(page);
|
|
await gotoAppShell(page);
|
|
await waitForSidebarHydration(page);
|
|
await openSessions(page);
|
|
await expectSessionRowArchived(page, agent.title);
|
|
|
|
// KEY ASSERTION: reproduce the screenshot state. Right after the daemon
|
|
// restart, with NO restore and NO row click, the rendered History table cells
|
|
// (fed by each agent row's projectPlacement via fetch_agent_history) must read
|
|
// the worktree branch and the worktree workspace name — never "main".
|
|
const branchCell = page.getByTestId(`agent-row-branch-${serverId}-${agent.id}`);
|
|
const workspaceCell = page.getByTestId(`agent-row-workspace-${serverId}-${agent.id}`);
|
|
|
|
await expect(branchCell).toBeVisible({ timeout: 60_000 });
|
|
await expect(branchCell).toHaveText(worktreeSlug, { timeout: 60_000 });
|
|
await expect(workspaceCell).toHaveText(worktree.workspaceName, { timeout: 60_000 });
|
|
await restoreWorkspaceFromHistory(page, serverId, agent.id);
|
|
await expectCommittedFileAfterReload(page, "restart-change.txt");
|
|
});
|
|
});
|