* fix(app): preserve desktop layout in half-screen windows Treat native window controls as physical corner obstructions instead of deriving clearance from compact layout state. Keep 751px windows in desktop mode while preserving usable content when persisted sidebars are wide. * fix(app): retain sidebar width across window resizes * fix(app): yield app sidebar to narrow settings * test(app): wait for mobile sidebar motion to settle * fix(app): yield navigation to narrow workspace explorer * fix(app): mark sidebar sizing helpers as worklets * fix(app): hide app navigation when space is constrained
211 lines
7.2 KiB
TypeScript
211 lines
7.2 KiB
TypeScript
import path from "node:path";
|
|
import { test, expect } from "./fixtures";
|
|
import { gotoAppShell } from "./helpers/app";
|
|
import {
|
|
closeMobileAgentSidebar,
|
|
expectMobileAgentSidebarHidden,
|
|
expectMobileAgentSidebarVisible,
|
|
openMobileAgentSidebar,
|
|
} from "./helpers/sidebar";
|
|
import { seedWorkspace } from "./helpers/seed-client";
|
|
import { expectWorkspaceHeader } from "./helpers/workspace-ui";
|
|
import { getServerId } from "./helpers/server-id";
|
|
import { escapeRegex } from "./helpers/regex";
|
|
|
|
const GITHUB_REMOTE_URL = "https://github.com/test-owner/test-repo.git";
|
|
|
|
function getWorkspaceRowTestId(workspaceId: string): string {
|
|
return `sidebar-workspace-row-${getServerId()}:${workspaceId}`;
|
|
}
|
|
|
|
async function openWorkspaceFromSidebar(
|
|
page: import("@playwright/test").Page,
|
|
workspaceId: string,
|
|
) {
|
|
const row = page.getByTestId(getWorkspaceRowTestId(workspaceId));
|
|
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
await row.click();
|
|
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
|
|
return row;
|
|
}
|
|
|
|
async function waitForSidebarProject(page: import("@playwright/test").Page, projectName: string) {
|
|
const row = page
|
|
.getByRole("button", {
|
|
name: new RegExp(escapeRegex(projectName), "i"),
|
|
})
|
|
.first();
|
|
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
return row;
|
|
}
|
|
|
|
async function waitForSidebarWorkspace(page: import("@playwright/test").Page, workspaceId: string) {
|
|
const row = page.getByTestId(getWorkspaceRowTestId(workspaceId));
|
|
await expect(row).toBeVisible({ timeout: 30_000 });
|
|
return row;
|
|
}
|
|
|
|
test.describe("Sidebar workspace list", () => {
|
|
test("project with GitHub remote shows owner/repo name in sidebar", async ({ page }) => {
|
|
const workspace = await seedWorkspace({
|
|
repoPrefix: "sidebar-remote-",
|
|
repo: { withRemote: true, originUrl: GITHUB_REMOTE_URL },
|
|
});
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarProject(page, "test-owner/test-repo");
|
|
await waitForSidebarWorkspace(page, workspace.workspaceId);
|
|
|
|
const projectRow = page
|
|
.locator('[data-testid^="sidebar-project-row-"]')
|
|
.filter({ hasText: "test-owner/test-repo" })
|
|
.first();
|
|
|
|
await expect(projectRow).toBeVisible({ timeout: 30_000 });
|
|
await expect(projectRow).not.toContainText(path.basename(workspace.repoPath));
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("project shows workspace under it", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "sidebar-workspace-under-project-" });
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
|
|
await waitForSidebarProject(page, path.basename(workspace.repoPath));
|
|
await waitForSidebarWorkspace(page, workspace.workspaceId);
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("non-git project shows directory name", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "sidebar-directory-", git: false });
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
|
|
const directoryName = path.basename(workspace.repoPath);
|
|
const projectRow = await waitForSidebarProject(page, directoryName);
|
|
await expect(projectRow).toContainText(directoryName);
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("workspace header shows correct title and subtitle", async ({ page }) => {
|
|
const workspace = await seedWorkspace({
|
|
repoPrefix: "sidebar-header-",
|
|
repo: { withRemote: true, originUrl: GITHUB_REMOTE_URL },
|
|
});
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarProject(page, "test-owner/test-repo");
|
|
await waitForSidebarWorkspace(page, workspace.workspaceId);
|
|
await openWorkspaceFromSidebar(page, workspace.workspaceId);
|
|
|
|
await expectWorkspaceHeader(page, {
|
|
title: workspace.workspaceName,
|
|
subtitle: "test-owner/test-repo",
|
|
});
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("git project shows branch name in workspace row", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "sidebar-branch-" });
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarProject(page, path.basename(workspace.repoPath));
|
|
|
|
expect(workspace.workspaceName).toBe("main");
|
|
await expect(await waitForSidebarWorkspace(page, workspace.workspaceId)).toContainText(
|
|
"main",
|
|
);
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
|
|
test("workspace hover card shows host as metadata", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "sidebar-hover-host-" });
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarProject(page, path.basename(workspace.repoPath));
|
|
|
|
const row = await waitForSidebarWorkspace(page, workspace.workspaceId);
|
|
await row.hover();
|
|
|
|
const hoverCard = page.getByTestId("workspace-hover-card");
|
|
await expect(hoverCard).toBeVisible({ timeout: 30_000 });
|
|
await expect(page.getByTestId("hover-card-workspace-host")).toHaveText("localhost");
|
|
await expect(hoverCard).not.toContainText(/\b(Online|Connecting|Offline|Error|Idle)\b/);
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe("Mobile sidebar panelState transition", () => {
|
|
test.use({ viewport: { width: 390, height: 844 } });
|
|
|
|
test("showMobileAgent open and close transition", async ({ page }) => {
|
|
await gotoAppShell(page);
|
|
await expectMobileAgentSidebarHidden(page);
|
|
await openMobileAgentSidebar(page);
|
|
await expectMobileAgentSidebarVisible(page);
|
|
await closeMobileAgentSidebar(page);
|
|
await expectMobileAgentSidebarHidden(page);
|
|
});
|
|
});
|
|
|
|
test.describe("Half-screen desktop layout", () => {
|
|
test.use({ viewport: { width: 751, height: 982 } });
|
|
|
|
test("keeps the pinned sidebar at half of a 14-inch Mac display", async ({ page }) => {
|
|
await gotoAppShell(page);
|
|
await expect(page.getByTestId("sidebar-global-new-workspace")).toBeVisible();
|
|
await expect(page.getByTestId("agent-list-backdrop")).not.toBeVisible();
|
|
});
|
|
|
|
test("yields app navigation to the settings split", async ({ page }) => {
|
|
await gotoAppShell(page);
|
|
await page.getByTestId("sidebar-settings").click();
|
|
|
|
await expect(page.getByTestId("settings-sidebar")).toBeVisible();
|
|
await expect(page.getByTestId("settings-detail-pane")).toBeVisible();
|
|
await expect(page.getByTestId("sidebar-settings")).not.toBeVisible();
|
|
});
|
|
|
|
test("yields app navigation to the Explorer", async ({ page }) => {
|
|
const workspace = await seedWorkspace({ repoPrefix: "sidebar-half-screen-explorer-" });
|
|
|
|
try {
|
|
await gotoAppShell(page);
|
|
await waitForSidebarProject(page, path.basename(workspace.repoPath));
|
|
await openWorkspaceFromSidebar(page, workspace.workspaceId);
|
|
|
|
await page.getByTestId("workspace-explorer-toggle").first().click();
|
|
await expect(
|
|
page.getByTestId("explorer-tab-files").filter({ visible: true }).first(),
|
|
).toBeVisible();
|
|
await expect(page.getByTestId("sidebar-global-new-workspace")).not.toBeVisible();
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
(await page.getByTestId("workspace-tabs-row").first().boundingBox())?.width ?? 0,
|
|
)
|
|
.toBeGreaterThanOrEqual(400);
|
|
} finally {
|
|
await workspace.cleanup();
|
|
}
|
|
});
|
|
});
|