Keep selected projects when switching hosts (#2700)
* fix(app): preserve project selection across hosts Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity. * fix(app): restore selected clone after host round trip Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection. * fix(app): key status icons by project view * fix(app): preserve origin during archive gaps
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { expectOpenedProject } from "./helpers/project-picker-ui";
|
||||
import { connectSeedClient, seedWorkspace } from "./helpers/seed-client";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { createTempGitRepo } from "./helpers/workspace";
|
||||
import { waitForSidebarHydration } from "./helpers/workspace-ui";
|
||||
|
||||
@@ -32,12 +33,12 @@ async function archiveWorkspaceFromSidebar(page: Page, workspaceId: string): Pro
|
||||
await archiveItem.click();
|
||||
}
|
||||
|
||||
async function removeProjectFromSidebar(page: Page, projectKey: string): Promise<void> {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectKey}`);
|
||||
async function removeProjectFromSidebar(page: Page, projectViewKey: string): Promise<void> {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectViewKey}`);
|
||||
await expect(projectRow).toBeVisible({ timeout: 30_000 });
|
||||
await projectRow.hover();
|
||||
|
||||
const kebab = page.getByTestId(`sidebar-project-kebab-${projectKey}`);
|
||||
const kebab = page.getByTestId(`sidebar-project-kebab-${projectViewKey}`);
|
||||
await expect(kebab).toBeVisible({ timeout: 10_000 });
|
||||
await kebab.click();
|
||||
|
||||
@@ -45,7 +46,7 @@ async function removeProjectFromSidebar(page: Page, projectKey: string): Promise
|
||||
// user-confirmed removal proceeds deterministically.
|
||||
page.once("dialog", (dialog) => void dialog.accept());
|
||||
|
||||
const removeItem = page.getByTestId(`sidebar-project-menu-remove-${projectKey}`);
|
||||
const removeItem = page.getByTestId(`sidebar-project-menu-remove-${projectViewKey}`);
|
||||
await expect(removeItem).toBeVisible({ timeout: 10_000 });
|
||||
await removeItem.click();
|
||||
}
|
||||
@@ -152,9 +153,10 @@ test.describe("Project with no workspaces persists", () => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "empty-project-persists-" });
|
||||
|
||||
try {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectKey}`);
|
||||
const projectViewKey = projectEquivalenceViewKey(workspace.projectKey);
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectViewKey}`);
|
||||
const newWorkspaceRow = page.getByTestId(
|
||||
`sidebar-project-new-workspace-row-${workspace.projectKey}`,
|
||||
`sidebar-project-new-workspace-row-${projectViewKey}`,
|
||||
);
|
||||
const globalNewWorkspace = page.getByTestId("sidebar-global-new-workspace");
|
||||
|
||||
@@ -198,7 +200,8 @@ test.describe("Project remove", () => {
|
||||
let readdedProjectId: string | null = null;
|
||||
|
||||
try {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectKey}`);
|
||||
const projectViewKey = projectEquivalenceViewKey(workspace.projectKey);
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectViewKey}`);
|
||||
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
@@ -207,7 +210,7 @@ test.describe("Project remove", () => {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await removeProjectFromSidebar(page, workspace.projectKey);
|
||||
await removeProjectFromSidebar(page, projectViewKey);
|
||||
|
||||
await expect(page.getByTestId(workspaceRowTestId(workspace.workspaceId))).toHaveCount(0, {
|
||||
timeout: 30_000,
|
||||
@@ -233,7 +236,9 @@ test.describe("Project remove", () => {
|
||||
await expect(projectRow).toContainText(workspace.projectDisplayName);
|
||||
await expect(projectRow).not.toContainText(workspace.repoPath);
|
||||
await expect(
|
||||
page.getByTestId(`sidebar-project-new-workspace-row-${readdedProjectKey}`),
|
||||
page.getByTestId(
|
||||
`sidebar-project-new-workspace-row-${projectEquivalenceViewKey(readdedProjectKey)}`,
|
||||
),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
} finally {
|
||||
if (readdedProjectId) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { DaemonClient as InternalDaemonClient } from "@getpaseo/client/inte
|
||||
import { decodeWorkspaceIdFromPathSegment } from "@/utils/host-routes";
|
||||
import { connectDaemonClient } from "./daemon-client-loader";
|
||||
import { daemonWsRoutePattern } from "./daemon-port";
|
||||
import { projectEquivalenceViewKey } from "./project-view-key";
|
||||
import { expectWorkspaceHeader } from "./workspace-ui";
|
||||
|
||||
type NewWorkspaceDaemonClient = Pick<
|
||||
@@ -167,11 +168,12 @@ export async function openNewWorkspaceComposer(
|
||||
page: Page,
|
||||
input: { projectKey: string; projectDisplayName: string },
|
||||
): Promise<void> {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${input.projectKey}`).first();
|
||||
const projectViewKey = projectEquivalenceViewKey(input.projectKey);
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectViewKey}`).first();
|
||||
await expect(projectRow).toBeVisible({ timeout: 30_000 });
|
||||
await projectRow.hover();
|
||||
|
||||
const button = page.getByTestId(`sidebar-project-new-worktree-${input.projectKey}`).first();
|
||||
const button = page.getByTestId(`sidebar-project-new-worktree-${projectViewKey}`).first();
|
||||
await expect(button).toBeVisible({ timeout: 30_000 });
|
||||
await button.click();
|
||||
|
||||
@@ -216,8 +218,10 @@ export async function expectNewWorkspaceDraft(page: Page, draft: string): Promis
|
||||
}
|
||||
|
||||
export async function selectNewWorkspaceHost(page: Page, hostLabel: string): Promise<void> {
|
||||
await page.getByTestId("host-picker-trigger").click();
|
||||
await page.getByText(hostLabel, { exact: true }).click();
|
||||
const trigger = page.getByTestId("host-picker-trigger");
|
||||
await trigger.click();
|
||||
await page.getByRole("button", { name: hostLabel, exact: true }).click();
|
||||
await expect(trigger).toContainText(hostLabel);
|
||||
}
|
||||
|
||||
export async function submitNewWorkspacePrompt(
|
||||
@@ -244,13 +248,14 @@ export async function clickNewWorkspaceButton(
|
||||
|
||||
export async function selectNewWorkspaceProject(
|
||||
page: Page,
|
||||
input: { projectKey: string; projectDisplayName: string },
|
||||
input: { projectKey: string; projectDisplayName: string; projectViewKey?: string },
|
||||
): Promise<void> {
|
||||
const trigger = page.getByTestId("new-workspace-project-picker-trigger");
|
||||
await expect(trigger).toBeVisible({ timeout: 30_000 });
|
||||
await trigger.click();
|
||||
|
||||
const option = page.getByTestId(`new-workspace-project-picker-option-${input.projectKey}`);
|
||||
const projectViewKey = input.projectViewKey ?? projectEquivalenceViewKey(input.projectKey);
|
||||
const option = page.getByTestId(`new-workspace-project-picker-option-${projectViewKey}`);
|
||||
await expect(option).toBeVisible({ timeout: 30_000 });
|
||||
await option.click();
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function projectEquivalenceViewKey(projectKey: string): string {
|
||||
return projectKey;
|
||||
}
|
||||
|
||||
export function projectPlacementViewKey(serverId: string, projectId: string): string {
|
||||
return JSON.stringify([serverId, projectId]);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { getE2EDaemonPort } from "./helpers/daemon-port";
|
||||
import { seedWorkspace, type SeededWorkspace } from "./helpers/seed-client";
|
||||
import { seedSavedSettingsHosts } from "./helpers/settings";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { clickArchiveWorkspaceMenuItem, expectWorkspaceAbsentFromSidebar } from "./helpers/sidebar";
|
||||
import { waitForSidebarHydration } from "./helpers/workspace-ui";
|
||||
|
||||
@@ -22,7 +23,7 @@ import { waitForSidebarHydration } from "./helpers/workspace-ui";
|
||||
// screen, and non-git projects never offer the worktree Isolation control.
|
||||
|
||||
function projectRow(page: import("@playwright/test").Page, projectKey: string) {
|
||||
return page.getByTestId(`sidebar-project-row-${projectKey}`);
|
||||
return page.getByTestId(`sidebar-project-row-${projectEquivalenceViewKey(projectKey)}`);
|
||||
}
|
||||
|
||||
test.describe("New workspace entry points", () => {
|
||||
@@ -195,7 +196,7 @@ test.describe("New workspace entry points", () => {
|
||||
// route-driven navigation targets a different project.
|
||||
await page.getByTestId("new-workspace-project-picker-trigger").click();
|
||||
const optionC = page.getByTestId(
|
||||
`new-workspace-project-picker-option-${projectC.projectKey}`,
|
||||
`new-workspace-project-picker-option-${projectEquivalenceViewKey(projectC.projectKey)}`,
|
||||
);
|
||||
await expect(optionC).toBeVisible({ timeout: 30_000 });
|
||||
await optionC.click();
|
||||
@@ -238,7 +239,7 @@ test.describe("New workspace entry points", () => {
|
||||
await expect(trigger).toBeVisible({ timeout: 30_000 });
|
||||
await trigger.click();
|
||||
const nonGitOption = page.getByTestId(
|
||||
`new-workspace-project-picker-option-${nonGitProject.projectKey}`,
|
||||
`new-workspace-project-picker-option-${projectEquivalenceViewKey(nonGitProject.projectKey)}`,
|
||||
);
|
||||
await expect(nonGitOption).toBeVisible({ timeout: 30_000 });
|
||||
await nonGitOption.click();
|
||||
@@ -251,7 +252,7 @@ test.describe("New workspace entry points", () => {
|
||||
// Switching to the git project on the same screen reveals the Isolation row.
|
||||
await trigger.click();
|
||||
const gitOption = page.getByTestId(
|
||||
`new-workspace-project-picker-option-${gitProject.projectKey}`,
|
||||
`new-workspace-project-picker-option-${projectEquivalenceViewKey(gitProject.projectKey)}`,
|
||||
);
|
||||
await expect(gitOption).toBeVisible({ timeout: 30_000 });
|
||||
await gitOption.click();
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { expect, test as base } from "./fixtures";
|
||||
import { openProjectDirectoryWithHosts } from "./helpers/project-grouping";
|
||||
import { projectPlacementViewKey } from "./helpers/project-view-key";
|
||||
import { type IsolatedHostDaemon, startIsolatedHostDaemon } from "./helpers/isolated-host-daemon";
|
||||
import {
|
||||
expectNewWorkspaceProjectSelected,
|
||||
openGlobalNewWorkspaceComposer,
|
||||
selectNewWorkspaceHost,
|
||||
selectNewWorkspaceProject,
|
||||
} from "./helpers/new-workspace";
|
||||
import { connectSeedClient, type SeedDaemonClient } from "./helpers/seed-client";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { switchWorkspaceViaSidebar } from "./helpers/workspace-ui";
|
||||
import { createTempGitRepo } from "./helpers/workspace";
|
||||
|
||||
const PRIMARY_HOST_LABEL = "Primary host";
|
||||
const SECONDARY_HOST_LABEL = "Secondary host";
|
||||
const SHARED_PROJECT_NAME = "Paseo";
|
||||
const SHARED_REMOTE_URL = "https://github.com/getpaseo/paseo.git";
|
||||
|
||||
interface CreatedProject {
|
||||
projectId: string;
|
||||
projectKey: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
interface CrossHostProjectScenario {
|
||||
contextWorkspaceId: string;
|
||||
selectedProjectViewKey: string;
|
||||
sharedProjectKey: string;
|
||||
secondaryHost: {
|
||||
serverId: string;
|
||||
label: string;
|
||||
port: number;
|
||||
};
|
||||
}
|
||||
|
||||
async function createProject(
|
||||
client: SeedDaemonClient,
|
||||
input: { projectPath: string; projectName: string; workspaceName: string },
|
||||
): Promise<CreatedProject> {
|
||||
const created = await client.createWorkspace({
|
||||
source: { kind: "directory", path: input.projectPath },
|
||||
title: input.workspaceName,
|
||||
});
|
||||
if (!created.workspace) {
|
||||
throw new Error(created.error ?? `Failed to create project ${input.projectName}`);
|
||||
}
|
||||
|
||||
await client.renameProject(created.workspace.projectId, input.projectName);
|
||||
const listed = await client.listProjects();
|
||||
const project = listed.projects.find(
|
||||
(candidate) => candidate.projectId === created.workspace?.projectId,
|
||||
);
|
||||
if (!project?.projectKey) {
|
||||
throw new Error(`Created project ${created.workspace.projectId} has no project key`);
|
||||
}
|
||||
|
||||
return {
|
||||
projectId: created.workspace.projectId,
|
||||
projectKey: project.projectKey,
|
||||
workspaceId: created.workspace.id,
|
||||
};
|
||||
}
|
||||
|
||||
async function openNewWorkspaceFromContextProject(
|
||||
page: Parameters<typeof openProjectDirectoryWithHosts>[0],
|
||||
scenario: CrossHostProjectScenario,
|
||||
): Promise<void> {
|
||||
await openProjectDirectoryWithHosts(page, {
|
||||
hosts: [scenario.secondaryHost],
|
||||
primaryLabel: PRIMARY_HOST_LABEL,
|
||||
});
|
||||
await expect(
|
||||
page.getByTestId(`sidebar-project-row-${scenario.selectedProjectViewKey}`),
|
||||
).toBeVisible();
|
||||
await switchWorkspaceViaSidebar({
|
||||
page,
|
||||
serverId: getServerId(),
|
||||
workspaceId: scenario.contextWorkspaceId,
|
||||
});
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await expectNewWorkspaceProjectSelected(page, "Context project");
|
||||
}
|
||||
|
||||
const test = base.extend<{ crossHostProject: CrossHostProjectScenario }>({
|
||||
crossHostProject: async ({ page: _page }, provide) => {
|
||||
const secondaryDaemon: IsolatedHostDaemon = await startIsolatedHostDaemon(
|
||||
"new-workspace-project-preservation-secondary",
|
||||
);
|
||||
const contextRepo = await createTempGitRepo("new-workspace-context-");
|
||||
const primarySharedRepo = await createTempGitRepo("new-workspace-paseo-primary-", {
|
||||
originUrl: SHARED_REMOTE_URL,
|
||||
});
|
||||
const primaryDuplicateRepo = await createTempGitRepo("new-workspace-paseo-duplicate-", {
|
||||
originUrl: SHARED_REMOTE_URL,
|
||||
});
|
||||
const secondaryFallbackRepo = await createTempGitRepo("new-workspace-fallback-");
|
||||
const secondarySharedRepo = await createTempGitRepo("new-workspace-paseo-secondary-", {
|
||||
originUrl: SHARED_REMOTE_URL,
|
||||
});
|
||||
const primaryClient = await connectSeedClient();
|
||||
const secondaryClient = await connectSeedClient({ port: secondaryDaemon.port });
|
||||
const primaryProjects: CreatedProject[] = [];
|
||||
const secondaryProjects: CreatedProject[] = [];
|
||||
|
||||
try {
|
||||
const contextProject = await createProject(primaryClient, {
|
||||
projectPath: contextRepo.path,
|
||||
projectName: "Context project",
|
||||
workspaceName: "Context workspace",
|
||||
});
|
||||
primaryProjects.push(contextProject);
|
||||
const primarySharedProject = await createProject(primaryClient, {
|
||||
projectPath: primarySharedRepo.path,
|
||||
projectName: SHARED_PROJECT_NAME,
|
||||
workspaceName: "Primary Paseo workspace",
|
||||
});
|
||||
primaryProjects.push(primarySharedProject);
|
||||
const primaryDuplicateProject = await createProject(primaryClient, {
|
||||
projectPath: primaryDuplicateRepo.path,
|
||||
projectName: "Paseo duplicate",
|
||||
workspaceName: "Duplicate Paseo workspace",
|
||||
});
|
||||
primaryProjects.push(primaryDuplicateProject);
|
||||
secondaryProjects.push(
|
||||
await createProject(secondaryClient, {
|
||||
projectPath: secondaryFallbackRepo.path,
|
||||
projectName: "Aardvark project",
|
||||
workspaceName: "Secondary fallback workspace",
|
||||
}),
|
||||
);
|
||||
const secondarySharedProject = await createProject(secondaryClient, {
|
||||
projectPath: secondarySharedRepo.path,
|
||||
projectName: SHARED_PROJECT_NAME,
|
||||
workspaceName: "Secondary Paseo workspace",
|
||||
});
|
||||
secondaryProjects.push(secondarySharedProject);
|
||||
|
||||
if (
|
||||
primarySharedProject.projectKey !== primaryDuplicateProject.projectKey ||
|
||||
primarySharedProject.projectKey !== secondarySharedProject.projectKey
|
||||
) {
|
||||
throw new Error("Expected the Paseo clones to have the same equivalence key");
|
||||
}
|
||||
|
||||
await provide({
|
||||
contextWorkspaceId: contextProject.workspaceId,
|
||||
selectedProjectViewKey: projectPlacementViewKey(
|
||||
getServerId(),
|
||||
primarySharedProject.projectId,
|
||||
),
|
||||
sharedProjectKey: primarySharedProject.projectKey,
|
||||
secondaryHost: {
|
||||
serverId: secondaryDaemon.serverId,
|
||||
label: SECONDARY_HOST_LABEL,
|
||||
port: secondaryDaemon.port,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
for (const project of primaryProjects) {
|
||||
await primaryClient.removeProject(project.projectId).catch(() => undefined);
|
||||
}
|
||||
for (const project of secondaryProjects) {
|
||||
await secondaryClient.removeProject(project.projectId).catch(() => undefined);
|
||||
}
|
||||
await primaryClient.close().catch(() => undefined);
|
||||
await secondaryClient.close().catch(() => undefined);
|
||||
await contextRepo.cleanup().catch(() => undefined);
|
||||
await primarySharedRepo.cleanup().catch(() => undefined);
|
||||
await primaryDuplicateRepo.cleanup().catch(() => undefined);
|
||||
await secondaryFallbackRepo.cleanup().catch(() => undefined);
|
||||
await secondarySharedRepo.cleanup().catch(() => undefined);
|
||||
await secondaryDaemon.close().catch(() => undefined);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
test.describe("New workspace host project preservation", () => {
|
||||
test.describe.configure({ timeout: 180_000 });
|
||||
|
||||
test("keeps the equivalent project selected when one host has another clone", async ({
|
||||
page,
|
||||
crossHostProject,
|
||||
}) => {
|
||||
await openNewWorkspaceFromContextProject(page, crossHostProject);
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: crossHostProject.sharedProjectKey,
|
||||
projectViewKey: crossHostProject.selectedProjectViewKey,
|
||||
projectDisplayName: SHARED_PROJECT_NAME,
|
||||
});
|
||||
await selectNewWorkspaceHost(page, SECONDARY_HOST_LABEL);
|
||||
|
||||
await expectNewWorkspaceProjectSelected(page, SHARED_PROJECT_NAME);
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "./helpers/schedule-fake-host";
|
||||
import { seedWorkspace, type SeededWorkspace } from "./helpers/seed-client";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { escapeRegex } from "./helpers/regex";
|
||||
import { expectNoTruncation } from "./helpers/no-truncation";
|
||||
import { expectSettled, expectStableHeight } from "./helpers/settled";
|
||||
@@ -205,7 +206,9 @@ test.describe("Schedules project target", () => {
|
||||
await expect(formSheet.getByText("Cron", { exact: true })).toHaveCount(0);
|
||||
|
||||
await page.getByRole("button", { name: /select project/i }).click();
|
||||
await page.getByTestId(`schedule-project-option-${workspace.projectKey}`).click();
|
||||
await page
|
||||
.getByTestId(`schedule-project-option-${projectEquivalenceViewKey(workspace.projectKey)}`)
|
||||
.click();
|
||||
const projectTrigger = page.getByTestId("schedule-project-trigger");
|
||||
await expect(projectTrigger).toContainText(workspace.projectDisplayName);
|
||||
await expectSettled(projectTrigger);
|
||||
@@ -315,7 +318,9 @@ test.describe("Schedules project target", () => {
|
||||
await expectSettled(hostTrigger);
|
||||
|
||||
await projectTrigger.click();
|
||||
await page.getByTestId(`schedule-project-option-${workspace.projectKey}`).click();
|
||||
await page
|
||||
.getByTestId(`schedule-project-option-${projectEquivalenceViewKey(workspace.projectKey)}`)
|
||||
.click();
|
||||
await expect(projectTrigger).toContainText(workspace.projectDisplayName);
|
||||
await expectSettled(projectTrigger);
|
||||
|
||||
@@ -337,7 +342,9 @@ test.describe("Schedules project target", () => {
|
||||
await expectSettled(projectTrigger);
|
||||
|
||||
await projectTrigger.click();
|
||||
await page.getByTestId(`schedule-project-option-${fakeHost.projectKey}`).click();
|
||||
await page
|
||||
.getByTestId(`schedule-project-option-${projectEquivalenceViewKey(fakeHost.projectKey)}`)
|
||||
.click();
|
||||
await expect(projectTrigger).toContainText(fakeHost.projectDisplayName);
|
||||
await expectSettled(projectTrigger);
|
||||
await expect(modelTrigger).toContainText(/select model/i);
|
||||
@@ -363,7 +370,9 @@ test.describe("Schedules project target", () => {
|
||||
await page.getByTestId("schedules-empty-new").click();
|
||||
|
||||
await page.getByRole("button", { name: /select project/i }).click();
|
||||
await page.getByTestId(`schedule-project-option-${workspace.projectKey}`).click();
|
||||
await page
|
||||
.getByTestId(`schedule-project-option-${projectEquivalenceViewKey(workspace.projectKey)}`)
|
||||
.click();
|
||||
await selectModelByLabel(page, "Ten second stream");
|
||||
await expect(
|
||||
page.getByText("Off keeps each run's workspace in the sidebar for inspection."),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { gotoWorkspace, clickNewTerminal } from "./helpers/launcher";
|
||||
import { seedWorkspace, type SeededWorkspace } from "./helpers/seed-client";
|
||||
import { seedMockAgentWorkspace } from "./helpers/mock-agent";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { waitForSidebarHydration } from "./helpers/workspace-ui";
|
||||
import { getVisibleWorkspaceAgentTabIds } from "./helpers/workspace-tabs";
|
||||
|
||||
@@ -17,11 +18,11 @@ function workspaceRow(page: Page, workspaceId: string) {
|
||||
}
|
||||
|
||||
function projectRow(page: Page, projectKey: string) {
|
||||
return page.getByTestId(`sidebar-project-row-${projectKey}`);
|
||||
return page.getByTestId(`sidebar-project-row-${projectEquivalenceViewKey(projectKey)}`);
|
||||
}
|
||||
|
||||
function projectNewWorktreeIcon(page: Page, projectKey: string) {
|
||||
return page.getByTestId(`sidebar-project-new-worktree-${projectKey}`);
|
||||
return page.getByTestId(`sidebar-project-new-worktree-${projectEquivalenceViewKey(projectKey)}`);
|
||||
}
|
||||
|
||||
async function seedSecondWorkspace(seeded: SeededWorkspace, title: string): Promise<string> {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { seedWorkspace } from "./helpers/seed-client";
|
||||
import { expectWorkspaceHeader } from "./helpers/workspace-ui";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { escapeRegex } from "./helpers/regex";
|
||||
|
||||
const GITHUB_REMOTE_URL = "https://github.com/test-owner/test-repo.git";
|
||||
@@ -219,7 +220,9 @@ test.describe("Half-screen desktop layout", () => {
|
||||
}
|
||||
|
||||
await gotoAppShell(page);
|
||||
await page.getByTestId(`sidebar-project-show-more-${workspace.projectKey}`).click();
|
||||
await page
|
||||
.getByTestId(`sidebar-project-show-more-${projectEquivalenceViewKey(workspace.projectKey)}`)
|
||||
.click();
|
||||
await waitForSidebarWorkspace(page, lastWorkspaceId);
|
||||
|
||||
const sidebarScroll = page.getByTestId("sidebar-project-workspace-list-scroll");
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { seedWorkspace, type SeededWorkspace } from "./helpers/seed-client";
|
||||
import { expectExplorerEntryVisible } from "./helpers/file-explorer";
|
||||
import { getServerId } from "./helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "./helpers/project-view-key";
|
||||
import { waitForSidebarHydration } from "./helpers/workspace-ui";
|
||||
|
||||
// Model B reshape: a workspace is the unit, its isolation (local checkout or
|
||||
@@ -190,9 +191,9 @@ test.describe("Workspace multiplicity creation flow", () => {
|
||||
await waitForSidebarHydration(page);
|
||||
// Model B: a non-git project is an expandable parent like any other, with
|
||||
// its single workspace already rendered as its own row underneath.
|
||||
await expect(page.getByTestId(`sidebar-project-row-${seeded.projectKey}`)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(
|
||||
page.getByTestId(`sidebar-project-row-${projectEquivalenceViewKey(seeded.projectKey)}`),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByTestId(workspaceRowTestId(seeded.workspaceId))).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
@@ -210,9 +211,9 @@ test.describe("Workspace multiplicity creation flow", () => {
|
||||
|
||||
// Both the original and the new workspace render as distinct rows under
|
||||
// the same expandable parent.
|
||||
await expect(page.getByTestId(`sidebar-project-row-${seeded.projectKey}`)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(
|
||||
page.getByTestId(`sidebar-project-row-${projectEquivalenceViewKey(seeded.projectKey)}`),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByTestId(workspaceRowTestId(seeded.workspaceId))).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user