diff --git a/packages/app/e2e/helpers/app.ts b/packages/app/e2e/helpers/app.ts index 71e505a6c..e3f7efea9 100644 --- a/packages/app/e2e/helpers/app.ts +++ b/packages/app/e2e/helpers/app.ts @@ -367,7 +367,7 @@ export const createAgent = async (page: Page, message: string) => { await input.press('Enter'); // Router updates can be same-document transitions; assert URL state instead of waiting for a commit event. - await expect(page).toHaveURL(/\/agent\//, { timeout: 30000 }); + await expect(page).toHaveURL(/\/(workspace|agent)\//, { timeout: 30000 }); await expect(page.getByText(message, { exact: true }).first()).toBeVisible({ timeout: 30000, }); diff --git a/packages/app/e2e/workspace-sidebar-content-sync.spec.ts b/packages/app/e2e/workspace-sidebar-content-sync.spec.ts new file mode 100644 index 000000000..a7bb1e6dd --- /dev/null +++ b/packages/app/e2e/workspace-sidebar-content-sync.spec.ts @@ -0,0 +1,103 @@ +import { execSync } from 'node:child_process'; +import { test, expect, type Page } from './fixtures'; +import { setWorkingDirectory } from './helpers/app'; +import { createTempGitRepo } from './helpers/workspace'; +import { buildHostWorkspaceRoute } from '@/utils/host-routes'; + +function escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function candidateWorkspaceIds(inputPath: string): string[] { + const trimmed = inputPath.replace(/\/+$/, ''); + const candidates = new Set([trimmed]); + if (trimmed.startsWith('/var/')) { + candidates.add(`/private${trimmed}`); + } + if (trimmed.startsWith('/private/var/')) { + candidates.add(trimmed.replace(/^\/private/, '')); + } + return Array.from(candidates); +} + +function workspaceRowLocator(page: Page, serverId: string, workspacePath: string) { + const ids = candidateWorkspaceIds(workspacePath).map( + (id) => `[data-testid="sidebar-workspace-row-${serverId}:${id}"]` + ); + return page.locator(ids.join(',')).first(); +} + +async function openNewAgentComposer(page: Page): Promise { + await page.goto('/'); + + const sidebarNewAgent = page.getByTestId('sidebar-new-agent').first(); + if (await sidebarNewAgent.isVisible().catch(() => false)) { + await sidebarNewAgent.click(); + } else { + await page.getByText('New agent', { exact: true }).first().click(); + } + + await expect(page.getByRole('textbox', { name: 'Message agent...' })).toBeVisible({ timeout: 30000 }); +} + +async function seedWorkspaceActivity(page: Page, marker: string): Promise { + const input = page.getByRole('textbox', { name: 'Message agent...' }); + await expect(input).toBeEditable({ timeout: 30000 }); + await input.fill(marker); + await input.press('Enter'); + await expect(page).toHaveURL(/\/workspace\//, { timeout: 30000 }); +} + +async function switchViaSidebar(input: { + page: Page; + serverId: string; + targetWorkspacePath: string; +}) { + const row = workspaceRowLocator(input.page, input.serverId, input.targetWorkspacePath); + await expect(row).toBeVisible({ timeout: 30000 }); + await row.click(); + + const targetWorkspaceRoute = buildHostWorkspaceRoute(input.serverId, input.targetWorkspacePath); + await expect(input.page).toHaveURL(new RegExp(escapeRegex(targetWorkspaceRoute)), { + timeout: 30000, + }); +} + +test('sidebar workspace switch keeps visible content in sync with selected workspace', async ({ page }) => { + const serverId = process.env.E2E_SERVER_ID; + if (!serverId) { + throw new Error('E2E_SERVER_ID is not set.'); + } + + const repoA = await createTempGitRepo('paseo-e2e-sync-a-'); + const repoB = await createTempGitRepo('paseo-e2e-sync-b-'); + + const tokenA = `SYNC_A_${Date.now()}`; + const tokenB = `SYNC_B_${Date.now()}`; + + try { + execSync('git checkout -b sync-a-branch', { cwd: repoA.path, stdio: 'ignore' }); + execSync('git checkout -b sync-b-branch', { cwd: repoB.path, stdio: 'ignore' }); + + await openNewAgentComposer(page); + await setWorkingDirectory(page, repoA.path); + await seedWorkspaceActivity(page, tokenA); + + await openNewAgentComposer(page); + await setWorkingDirectory(page, repoB.path); + await seedWorkspaceActivity(page, tokenB); + + await page.goto(buildHostWorkspaceRoute(serverId, repoA.path)); + await expect(page).toHaveURL(new RegExp('/workspace/'), { timeout: 30000 }); + await expect(page.getByText('sync-a-branch', { exact: true }).first()).toBeVisible({ timeout: 30000 }); + + await switchViaSidebar({ page, serverId, targetWorkspacePath: repoB.path }); + await expect(page.getByText('sync-b-branch', { exact: true }).first()).toBeVisible({ timeout: 30000 }); + + await switchViaSidebar({ page, serverId, targetWorkspacePath: repoA.path }); + await expect(page.getByText('sync-a-branch', { exact: true }).first()).toBeVisible({ timeout: 30000 }); + } finally { + await repoA.cleanup(); + await repoB.cleanup(); + } +}); diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx index 2437debbe..ce7b53350 100644 --- a/packages/app/src/components/sidebar-workspace-list.tsx +++ b/packages/app/src/components/sidebar-workspace-list.tsx @@ -792,7 +792,7 @@ function ProjectBlock({ return } onWorkspacePress?.() - router.push(workspaceRoute as any) + router.replace(workspaceRoute as any) }} drag={workspaceDrag} isDragging={isActive}