mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
test(projects): use grouped identity in UI specs
This commit is contained in:
@@ -207,7 +207,7 @@ test.describe("Composer attachments", () => {
|
||||
});
|
||||
|
||||
await openNewWorkspaceComposer(page, {
|
||||
projectKey: workspace.projectId,
|
||||
projectKey: workspace.projectKey,
|
||||
projectDisplayName: workspace.projectDisplayName,
|
||||
});
|
||||
|
||||
@@ -293,7 +293,7 @@ test.describe("Composer attachments", () => {
|
||||
});
|
||||
|
||||
await openNewWorkspaceComposer(page, {
|
||||
projectKey: workspace.projectId,
|
||||
projectKey: workspace.projectKey,
|
||||
projectDisplayName: workspace.projectDisplayName,
|
||||
});
|
||||
await fillComposerDraft(page, "lock test prompt");
|
||||
|
||||
@@ -32,12 +32,12 @@ async function archiveWorkspaceFromSidebar(page: Page, workspaceId: string): Pro
|
||||
await archiveItem.click();
|
||||
}
|
||||
|
||||
async function removeProjectFromSidebar(page: Page, projectId: string): Promise<void> {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectId}`);
|
||||
async function removeProjectFromSidebar(page: Page, projectKey: string): Promise<void> {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${projectKey}`);
|
||||
await expect(projectRow).toBeVisible({ timeout: 30_000 });
|
||||
await projectRow.hover();
|
||||
|
||||
const kebab = page.getByTestId(`sidebar-project-kebab-${projectId}`);
|
||||
const kebab = page.getByTestId(`sidebar-project-kebab-${projectKey}`);
|
||||
await expect(kebab).toBeVisible({ timeout: 10_000 });
|
||||
await kebab.click();
|
||||
|
||||
@@ -45,7 +45,7 @@ async function removeProjectFromSidebar(page: Page, projectId: string): Promise<
|
||||
// user-confirmed removal proceeds deterministically.
|
||||
page.once("dialog", (dialog) => void dialog.accept());
|
||||
|
||||
const removeItem = page.getByTestId(`sidebar-project-menu-remove-${projectId}`);
|
||||
const removeItem = page.getByTestId(`sidebar-project-menu-remove-${projectKey}`);
|
||||
await expect(removeItem).toBeVisible({ timeout: 10_000 });
|
||||
await removeItem.click();
|
||||
}
|
||||
@@ -152,9 +152,9 @@ test.describe("Project with no workspaces persists", () => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "empty-project-persists-" });
|
||||
|
||||
try {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectId}`);
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectKey}`);
|
||||
const newWorkspaceRow = page.getByTestId(
|
||||
`sidebar-project-new-workspace-row-${workspace.projectId}`,
|
||||
`sidebar-project-new-workspace-row-${workspace.projectKey}`,
|
||||
);
|
||||
const globalNewWorkspace = page.getByTestId("sidebar-global-new-workspace");
|
||||
|
||||
@@ -197,7 +197,7 @@ test.describe("Project remove", () => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "project-remove-sidebar-" });
|
||||
|
||||
try {
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectId}`);
|
||||
const projectRow = page.getByTestId(`sidebar-project-row-${workspace.projectKey}`);
|
||||
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
@@ -206,7 +206,7 @@ test.describe("Project remove", () => {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await removeProjectFromSidebar(page, workspace.projectId);
|
||||
await removeProjectFromSidebar(page, workspace.projectKey);
|
||||
|
||||
await expect(page.getByTestId(workspaceRowTestId(workspace.workspaceId))).toHaveCount(0, {
|
||||
timeout: 30_000,
|
||||
|
||||
@@ -46,9 +46,13 @@ function requireWorkspace(payload: WorkspacePayload) {
|
||||
}
|
||||
|
||||
function openedProjectFromWorkspace(workspace: WorkspaceDescriptor): OpenedProject {
|
||||
const projectKey = workspace.projectGroupKey ?? workspace.project?.projectGroupKey;
|
||||
if (!projectKey) {
|
||||
throw new Error(`Workspace ${workspace.id} has no project group key`);
|
||||
}
|
||||
return {
|
||||
workspaceId: workspace.id,
|
||||
projectKey: workspace.projectId,
|
||||
projectKey,
|
||||
projectDisplayName: workspace.projectDisplayName,
|
||||
workspaceName: workspace.name,
|
||||
workspaceDirectory: workspace.workspaceDirectory,
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface SeedWorkspaceDescriptor {
|
||||
id: string;
|
||||
name: string;
|
||||
projectId: string;
|
||||
projectGroupKey?: string;
|
||||
project?: { projectGroupKey?: string };
|
||||
projectDisplayName: string;
|
||||
projectRootPath: string;
|
||||
workspaceDirectory: string;
|
||||
@@ -179,8 +181,10 @@ export interface SeededWorkspace {
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
workspaceDirectory: string;
|
||||
/** Stable project identity the daemon groups workspaces under. */
|
||||
/** Host-local identity used by daemon project operations. */
|
||||
projectId: string;
|
||||
/** Opaque cross-host key used by grouped project UI and routes. */
|
||||
projectKey: string;
|
||||
/** Project label the UI shows (owner/repo for known remotes, else basename). */
|
||||
projectDisplayName: string;
|
||||
cleanup(): Promise<void>;
|
||||
@@ -208,6 +212,10 @@ export async function seedWorkspace(options: {
|
||||
throw new Error(created.error ?? `Failed to create workspace ${project.path}`);
|
||||
}
|
||||
const workspace = created.workspace;
|
||||
const projectKey = workspace.projectGroupKey ?? workspace.project?.projectGroupKey;
|
||||
if (!projectKey) {
|
||||
throw new Error(`Created workspace ${workspace.id} has no project group key`);
|
||||
}
|
||||
return {
|
||||
client,
|
||||
repoPath: project.path,
|
||||
@@ -215,6 +223,7 @@ export async function seedWorkspace(options: {
|
||||
workspaceName: workspace.name,
|
||||
workspaceDirectory: workspace.workspaceDirectory,
|
||||
projectId: workspace.projectId,
|
||||
projectKey,
|
||||
projectDisplayName: workspace.projectDisplayName,
|
||||
cleanup: async () => {
|
||||
await client.removeProject(workspace.projectId).catch(() => undefined);
|
||||
|
||||
@@ -64,6 +64,14 @@ async function seedPaseoWorkspaceWithOpenCodeSession(): Promise<OpenCodeImportSc
|
||||
if (!createdWorkspace.workspace) {
|
||||
throw new Error(createdWorkspace.error ?? `Failed to create workspace ${PASEO_REPO_PATH}`);
|
||||
}
|
||||
const projectKey =
|
||||
createdWorkspace.workspace.projectGroupKey ??
|
||||
createdWorkspace.workspace.project?.projectGroupKey;
|
||||
if (!projectKey) {
|
||||
throw new Error(
|
||||
`Created workspace ${createdWorkspace.workspace.id} has no project group key`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
prompt,
|
||||
promptPreview,
|
||||
@@ -75,6 +83,7 @@ async function seedPaseoWorkspaceWithOpenCodeSession(): Promise<OpenCodeImportSc
|
||||
workspaceName: createdWorkspace.workspace.name,
|
||||
workspaceDirectory: createdWorkspace.workspace.workspaceDirectory,
|
||||
projectId: createdWorkspace.workspace.projectId,
|
||||
projectKey,
|
||||
projectDisplayName: createdWorkspace.workspace.projectDisplayName,
|
||||
cleanup: async () => {
|
||||
await client.close().catch(() => undefined);
|
||||
|
||||
@@ -165,7 +165,7 @@ test.describe("New workspace Codex mode preferences", () => {
|
||||
await waitForSidebarHydration(page);
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
});
|
||||
|
||||
@@ -221,7 +221,7 @@ test.describe("New workspace Codex mode preferences", () => {
|
||||
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ test.describe("New workspace composer draft", () => {
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
await openNewWorkspaceComposer(page, {
|
||||
projectKey: firstProject.projectId,
|
||||
projectKey: firstProject.projectKey,
|
||||
projectDisplayName: firstProject.projectDisplayName,
|
||||
});
|
||||
await expectNewWorkspaceProjectSelected(page, firstProject.projectDisplayName);
|
||||
@@ -42,7 +42,7 @@ test.describe("New workspace composer draft", () => {
|
||||
await fillNewWorkspaceDraft(page, DRAFT);
|
||||
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: secondProject.projectId,
|
||||
projectKey: secondProject.projectKey,
|
||||
projectDisplayName: secondProject.projectDisplayName,
|
||||
});
|
||||
|
||||
|
||||
@@ -179,13 +179,13 @@ test.describe("New workspace entry points", () => {
|
||||
try {
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
await expect(projectRow(page, projectA.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, projectB.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, projectC.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, projectA.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, projectB.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, projectC.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Project A's row icon opens New Workspace with A preselected.
|
||||
await openNewWorkspaceComposer(page, {
|
||||
projectKey: projectA.projectId,
|
||||
projectKey: projectA.projectKey,
|
||||
projectDisplayName: projectA.projectDisplayName,
|
||||
});
|
||||
await expectNewWorkspaceProjectSelected(page, projectA.projectDisplayName);
|
||||
@@ -194,7 +194,9 @@ test.describe("New workspace entry points", () => {
|
||||
// manualProjectKey is what the reused 'new' screen must reset when the next
|
||||
// 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.projectId}`);
|
||||
const optionC = page.getByTestId(
|
||||
`new-workspace-project-picker-option-${projectC.projectKey}`,
|
||||
);
|
||||
await expect(optionC).toBeVisible({ timeout: 30_000 });
|
||||
await optionC.click();
|
||||
await expectNewWorkspaceProjectSelected(page, projectC.projectDisplayName);
|
||||
@@ -203,7 +205,7 @@ test.describe("New workspace entry points", () => {
|
||||
// because the stale manual choice (C) was reset on the route change. If the
|
||||
// reset were missing, the trigger would still read C.
|
||||
await openNewWorkspaceComposer(page, {
|
||||
projectKey: projectB.projectId,
|
||||
projectKey: projectB.projectKey,
|
||||
projectDisplayName: projectB.projectDisplayName,
|
||||
});
|
||||
await expectNewWorkspaceProjectSelected(page, projectB.projectDisplayName);
|
||||
@@ -226,8 +228,8 @@ test.describe("New workspace entry points", () => {
|
||||
try {
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
await expect(projectRow(page, gitProject.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, nonGitProject.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, gitProject.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, nonGitProject.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Open New Workspace for the non-git project via the global button, then
|
||||
// select it in the picker (the per-row icon would preselect it too).
|
||||
@@ -236,7 +238,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.projectId}`,
|
||||
`new-workspace-project-picker-option-${nonGitProject.projectKey}`,
|
||||
);
|
||||
await expect(nonGitOption).toBeVisible({ timeout: 30_000 });
|
||||
await nonGitOption.click();
|
||||
@@ -249,7 +251,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.projectId}`,
|
||||
`new-workspace-project-picker-option-${gitProject.projectKey}`,
|
||||
);
|
||||
await expect(gitOption).toBeVisible({ timeout: 30_000 });
|
||||
await gitOption.click();
|
||||
|
||||
@@ -126,7 +126,7 @@ test.describe("New Workspace mode cycle safety", () => {
|
||||
// so its handler is still registered when we cycle here.
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
});
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ test.describe("Model B sidebar shape", () => {
|
||||
|
||||
// Both projects are expandable parents — the non-git one is NOT flattened
|
||||
// into a bare workspace link.
|
||||
await expect(projectRow(page, gitProject.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, nonGitProject.projectId)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, gitProject.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
await expect(projectRow(page, nonGitProject.projectKey)).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Each parent shows both of its workspace rows underneath.
|
||||
await expect(workspaceRow(page, gitProject.workspaceId)).toBeVisible({ timeout: 30_000 });
|
||||
@@ -65,12 +65,12 @@ test.describe("Model B sidebar shape", () => {
|
||||
// Both projects show a per-row New workspace icon (revealed on hover): the
|
||||
// git project can branch off a worktree, and the non-git project can add
|
||||
// another workspace because the host supports workspaceMultiplicity.
|
||||
await projectRow(page, gitProject.projectId).hover();
|
||||
await expect(projectNewWorktreeIcon(page, gitProject.projectId)).toBeVisible({
|
||||
await projectRow(page, gitProject.projectKey).hover();
|
||||
await expect(projectNewWorktreeIcon(page, gitProject.projectKey)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await projectRow(page, nonGitProject.projectId).hover();
|
||||
await expect(projectNewWorktreeIcon(page, nonGitProject.projectId)).toBeVisible({
|
||||
await projectRow(page, nonGitProject.projectKey).hover();
|
||||
await expect(projectNewWorktreeIcon(page, nonGitProject.projectKey)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ test.describe("Half-screen desktop layout", () => {
|
||||
}
|
||||
|
||||
await gotoAppShell(page);
|
||||
await page.getByTestId(`sidebar-project-show-more-${workspace.projectId}`).click();
|
||||
await page.getByTestId(`sidebar-project-show-more-${workspace.projectKey}`).click();
|
||||
await waitForSidebarWorkspace(page, lastWorkspaceId);
|
||||
|
||||
const sidebarScroll = page.getByTestId("sidebar-project-workspace-list-scroll");
|
||||
|
||||
@@ -32,6 +32,8 @@ interface RestartDaemonClient {
|
||||
name: string;
|
||||
status: string;
|
||||
workspaceDirectory: string;
|
||||
projectGroupKey?: string;
|
||||
project?: { projectGroupKey?: string };
|
||||
}>;
|
||||
}>;
|
||||
fetchAgents(options?: { scope?: "active" }): Promise<{
|
||||
@@ -461,9 +463,18 @@ test.describe("Workspace model restart regressions", () => {
|
||||
.poll(() => getVisibleWorkspaceAgentTabIds(page), { timeout: 30_000 })
|
||||
.toContain(`workspace-tab-agent_${LEGACY_AGENT_ID}`);
|
||||
|
||||
const reconciledWorkspace = (await client.fetchWorkspaces()).entries.find(
|
||||
(workspace) => workspace.id === seeded.workspaceA,
|
||||
);
|
||||
const reconciledProjectKey =
|
||||
reconciledWorkspace?.projectGroupKey ?? reconciledWorkspace?.project?.projectGroupKey;
|
||||
if (!reconciledProjectKey) {
|
||||
throw new Error(`Workspace ${seeded.workspaceA} was not reconciled with a project key`);
|
||||
}
|
||||
|
||||
await openGlobalNewWorkspaceComposer(page);
|
||||
await selectNewWorkspaceProject(page, {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: reconciledProjectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
});
|
||||
await expectNewWorkspaceProjectSelected(page, seeded.projectDisplayName);
|
||||
|
||||
@@ -84,7 +84,7 @@ test.describe("Workspace multiplicity creation flow", () => {
|
||||
|
||||
try {
|
||||
const project = {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ test.describe("Workspace multiplicity creation flow", () => {
|
||||
|
||||
try {
|
||||
const project = {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
};
|
||||
|
||||
@@ -182,7 +182,7 @@ test.describe("Workspace multiplicity creation flow", () => {
|
||||
|
||||
try {
|
||||
const project = {
|
||||
projectKey: seeded.projectId,
|
||||
projectKey: seeded.projectKey,
|
||||
projectDisplayName: seeded.projectDisplayName,
|
||||
};
|
||||
|
||||
@@ -190,7 +190,7 @@ 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.projectId}`)).toBeVisible({
|
||||
await expect(page.getByTestId(`sidebar-project-row-${seeded.projectKey}`)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.getByTestId(workspaceRowTestId(seeded.workspaceId))).toBeVisible({
|
||||
@@ -210,7 +210,7 @@ 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.projectId}`)).toBeVisible({
|
||||
await expect(page.getByTestId(`sidebar-project-row-${seeded.projectKey}`)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.getByTestId(workspaceRowTestId(seeded.workspaceId))).toBeVisible({
|
||||
|
||||
Reference in New Issue
Block a user