Make the workspace right pane a first-class tab host (#3287)
* feat(app): add persistent workspace side panes Keep the common review and file-browsing workflow beside the active agent without tearing down pane contents. Workspace tabs now share one compact chip language, assistant file links route into the retained right pane, and diff/file trees share stable geometry. * fix(app): localize pull request empty state * test(app): update explorer pane coverage * fix(app): preserve explorer pane workflows * test(app): make explorer tab selection idempotent * fix(app): persist workspace review pins * fix(app): preserve sidebar scrim drag hook * fix(app): restore companion pane interactions * test(app): follow selected sidebar surface
This commit is contained in:
@@ -2,6 +2,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { test, expect, type Page } from "../support/fixtures";
|
||||
import { seedMockAgentWorkspace, openAgentRoute } from "../support/helpers/mock-agent";
|
||||
import { openChangesPanel } from "../support/helpers/workspace-tabs";
|
||||
|
||||
function visibleComposer(page: Page) {
|
||||
return page.locator("textarea[data-composer-input]").filter({ visible: true }).first();
|
||||
@@ -31,8 +32,7 @@ test("adds a changed file to the focused chat without replacing its composer dra
|
||||
await expect(agentComposer).toBeEditable({ timeout: 30_000 });
|
||||
await agentComposer.fill("Preserve this thought");
|
||||
|
||||
await page.getByRole("button", { name: "Open explorer" }).click();
|
||||
await page.getByTestId("explorer-tab-changes").click();
|
||||
await openChangesPanel(page);
|
||||
const changedFile = page.getByText("changed file.ts", { exact: true }).first();
|
||||
await expect(changedFile).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
|
||||
@@ -24,7 +24,10 @@ import {
|
||||
} from "../support/helpers/composer";
|
||||
import { openAgentRoute, seedMockAgentWorkspace } from "../support/helpers/mock-agent";
|
||||
import { seedWorkspace } from "../support/helpers/seed-client";
|
||||
import { waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
|
||||
import {
|
||||
createAgentTabFromMenu,
|
||||
waitForWorkspaceTabsVisible,
|
||||
} from "../support/helpers/workspace-tabs";
|
||||
import { getServerId } from "../support/helpers/server-id";
|
||||
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
|
||||
import { WORKSPACE_DECK_MAX_MOUNTED_WORKSPACES } from "@/screens/workspace/workspace-deck-retention";
|
||||
@@ -755,7 +758,7 @@ async function expectRenderedBefore(first: Locator, second: Locator): Promise<vo
|
||||
async function openWorkspaceDraft(page: Page, workspaceId: string): Promise<void> {
|
||||
await page.goto(buildHostWorkspaceRoute(getServerId(), workspaceId));
|
||||
await waitForWorkspaceTabsVisible(page);
|
||||
await page.getByTestId("workspace-new-agent-tab-inline").click();
|
||||
await createAgentTabFromMenu(page);
|
||||
await expectComposerVisible(page);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ test("keeps the selected workspace visible in Pure black", async ({ page }, test
|
||||
await row.click();
|
||||
|
||||
await expect(row).toHaveAttribute("aria-selected", "true");
|
||||
await expect(row).toHaveCSS("background-color", "rgb(22, 22, 22)");
|
||||
await expect(row).toHaveCSS("background-color", "rgb(17, 17, 17)");
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath("pure-black-selected-workspace.png"),
|
||||
fullPage: true,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { daemonWsRoutePattern } from "../support/helpers/daemon-port";
|
||||
import { getServerId } from "../support/helpers/server-id";
|
||||
import { connectSeedClient } from "../support/helpers/seed-client";
|
||||
import { createTempGitRepo } from "../support/helpers/workspace";
|
||||
import { waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
|
||||
import { openChangesPanel, waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
|
||||
|
||||
interface DirtyWorkspace {
|
||||
id: string;
|
||||
@@ -29,6 +29,25 @@ interface CleanupTask {
|
||||
const cleanupTasks: CleanupTask[] = [];
|
||||
const APP_SETTINGS_KEY = "@paseo:app-settings";
|
||||
|
||||
function changesTree(page: Page) {
|
||||
return page.getByTestId("changes-tree-rail-tree");
|
||||
}
|
||||
|
||||
function changesContent(page: Page) {
|
||||
return page.getByTestId("changes-tree-rail-content");
|
||||
}
|
||||
|
||||
async function readFileIfPresent(filePath: string): Promise<string | null> {
|
||||
try {
|
||||
return await readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function failNextDiscardRequest(page: Page): Promise<void> {
|
||||
await page.routeWebSocket(daemonWsRoutePattern(), (browserSocket) => {
|
||||
const serverSocket = browserSocket.connectToServer();
|
||||
@@ -266,7 +285,7 @@ test("changes file actions open below the right-click without a reserved kebab",
|
||||
await expect(page.getByTestId("diff-file-0-open-file")).toBeVisible();
|
||||
const menuBounds = await page.getByTestId("diff-file-0-context-menu").boundingBox();
|
||||
expect(menuBounds).not.toBeNull();
|
||||
expect(menuBounds!.x).toBeCloseTo(fileRowBounds!.x + 80, 0);
|
||||
expect(Math.abs(menuBounds!.x - (fileRowBounds!.x + 80))).toBeLessThanOrEqual(1);
|
||||
expect(menuBounds!.y).toBeGreaterThan(fileRowBounds!.y + 10);
|
||||
await page.getByTestId("diff-file-0-open-file").click();
|
||||
|
||||
@@ -282,14 +301,14 @@ test("changes context menus duplicate files and folders", async ({ page }) => {
|
||||
await page.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
await page.getByTestId("diff-file-0-duplicate").click();
|
||||
await expect
|
||||
.poll(() => readFile(path.join(workspace.repoPath, "src/use-mounted-tab-set copy.ts"), "utf8"))
|
||||
.poll(() => readFileIfPresent(path.join(workspace.repoPath, "src/use-mounted-tab-set copy.ts")))
|
||||
.toBe(AFTER);
|
||||
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await page.getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
await changesTree(page).getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
await page.getByTestId("diff-folder-src-duplicate").click();
|
||||
await expect
|
||||
.poll(() => readFile(path.join(workspace.repoPath, "src copy/use-mounted-tab-set.ts"), "utf8"))
|
||||
.poll(() => readFileIfPresent(path.join(workspace.repoPath, "src copy/use-mounted-tab-set.ts")))
|
||||
.toBe(AFTER);
|
||||
});
|
||||
|
||||
@@ -299,24 +318,25 @@ test("changes context menu recursively collapses descendant folders", async ({ p
|
||||
await openWorkspaceChanges(page, workspace);
|
||||
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await expect(page.getByTestId("diff-folder-src/zz-folder")).toBeVisible();
|
||||
await expect(page.getByTestId("diff-folder-src/zz-folder/nested")).toBeVisible();
|
||||
const tree = changesTree(page);
|
||||
await expect(tree.getByTestId("diff-folder-src/zz-folder")).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-folder-src/zz-folder/nested")).toBeVisible();
|
||||
|
||||
await page.getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
await tree.getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
await page.getByTestId("diff-folder-src-collapse-folder").click();
|
||||
await expect(page.getByTestId("diff-folder-src/zz-folder")).toHaveCount(0);
|
||||
await expect(tree.getByTestId("diff-folder-src/zz-folder")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("diff-folder-src-toggle").click();
|
||||
await expect(page.getByTestId("diff-folder-src/zz-folder")).toBeVisible();
|
||||
await expect(page.getByText("root.ts", { exact: true })).toHaveCount(0);
|
||||
await tree.getByTestId("diff-folder-src-toggle").click();
|
||||
await expect(tree.getByTestId("diff-folder-src/zz-folder")).toBeVisible();
|
||||
await expect(tree.getByText("root.ts", { exact: true })).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("diff-folder-src/zz-folder-toggle").click();
|
||||
await expect(page.getByText("root.ts", { exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("diff-folder-src/zz-folder/nested")).toBeVisible();
|
||||
await expect(page.getByText("changed.ts", { exact: true })).toHaveCount(0);
|
||||
await tree.getByTestId("diff-folder-src/zz-folder-toggle").click();
|
||||
await expect(tree.getByText("root.ts", { exact: true })).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-folder-src/zz-folder/nested")).toBeVisible();
|
||||
await expect(tree.getByText("changed.ts", { exact: true })).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("diff-folder-src/zz-folder/nested-toggle").click();
|
||||
await expect(page.getByText("changed.ts", { exact: true })).toBeVisible();
|
||||
await tree.getByTestId("diff-folder-src/zz-folder/nested-toggle").click();
|
||||
await expect(tree.getByText("changed.ts", { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test("changes context menus expose folder revert and restore a file after confirmation", async ({
|
||||
@@ -327,7 +347,8 @@ test("changes context menus expose folder revert and restore a file after confir
|
||||
await openWorkspaceChanges(page, workspace);
|
||||
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await page.getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
const tree = changesTree(page);
|
||||
await tree.getByTestId("diff-folder-src-toggle").click({ button: "right" });
|
||||
const folderRevert = page.getByTestId("diff-folder-src-revert");
|
||||
await expect(folderRevert).toBeVisible();
|
||||
const revertLabelColor = await folderRevert
|
||||
@@ -336,7 +357,7 @@ test("changes context menus expose folder revert and restore a file after confir
|
||||
await expect(folderRevert.locator("svg")).toHaveCSS("stroke", revertLabelColor);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
await tree.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
const cancelledConfirmation = new Promise<string>((resolve) => {
|
||||
page.once("dialog", async (dialog) => {
|
||||
const message = dialog.message();
|
||||
@@ -346,12 +367,12 @@ test("changes context menus expose folder revert and restore a file after confir
|
||||
});
|
||||
await page.getByTestId("diff-file-0-revert").click();
|
||||
expect(await cancelledConfirmation).toContain("src/use-mounted-tab-set.ts");
|
||||
await expect(page.getByTestId("diff-file-0")).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-file-0")).toBeVisible();
|
||||
await expect
|
||||
.poll(() => readFile(path.join(workspace.repoPath, "src/use-mounted-tab-set.ts"), "utf8"))
|
||||
.toBe(AFTER);
|
||||
|
||||
await page.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
await tree.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
const confirmation = new Promise<string>((resolve) => {
|
||||
page.once("dialog", async (dialog) => {
|
||||
const message = dialog.message();
|
||||
@@ -362,7 +383,7 @@ test("changes context menus expose folder revert and restore a file after confir
|
||||
await page.getByTestId("diff-file-0-revert").click();
|
||||
expect(await confirmation).toContain("src/use-mounted-tab-set.ts");
|
||||
|
||||
await expect(page.getByTestId("diff-file-0")).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect(tree.getByTestId("diff-file-0")).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect
|
||||
.poll(() => readFile(path.join(workspace.repoPath, "src/use-mounted-tab-set.ts"), "utf8"))
|
||||
.toBe(BEFORE);
|
||||
@@ -451,49 +472,39 @@ test("shows a revert error returned by the daemon", async ({ page }) => {
|
||||
.toBe(AFTER);
|
||||
});
|
||||
|
||||
test("Changes switches between inline and full-tab navigation", async ({ page }) => {
|
||||
test("Changes keeps review navigation and controls inside its workspace tab", async ({ page }) => {
|
||||
const workspace = await createWorkspaceWithMountedTabDiff({ includeDeletedFile: true });
|
||||
await useUnwrappedDiffLines(page);
|
||||
await openWorkspaceChanges(page, workspace);
|
||||
|
||||
const changesTabToggle = page.getByTestId("changes-open-tab");
|
||||
await expect(changesTabToggle).toHaveAccessibleName("Open Changes tab");
|
||||
await changesTabToggle.click();
|
||||
await expect(changesTabToggle).toHaveAccessibleName("Close Changes tab");
|
||||
|
||||
const visiblePanel = page.getByTestId("working-diff-panel").filter({ visible: true });
|
||||
await expect(visiblePanel).toBeVisible();
|
||||
await expect(visiblePanel.getByText("use-mounted-tab-set.ts", { exact: true })).toBeVisible();
|
||||
await expect(visiblePanel).toContainText("zz-deleted.ts");
|
||||
await expect(visiblePanel.getByTestId("changes-open-tab")).toHaveCount(0);
|
||||
await expect(visiblePanel.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
await expect(page.getByTestId("workspace-file-pane")).toHaveCount(0);
|
||||
await visiblePanel.getByTestId("diff-file-0-toggle").click();
|
||||
await expect(visiblePanel.getByTestId("diff-file-0-body")).toHaveCount(0);
|
||||
await visiblePanel.getByTestId("diff-file-0-toggle").click();
|
||||
await expect(visiblePanel.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
const workingDiffLayoutToggle = visiblePanel.getByTestId("working-diff-toggle-layout");
|
||||
const workingDiffLayoutToggle = visiblePanel.getByRole("button", {
|
||||
name: "Switch to side-by-side diff",
|
||||
});
|
||||
await expect(workingDiffLayoutToggle).toHaveAccessibleName("Switch to side-by-side diff");
|
||||
await workingDiffLayoutToggle.click();
|
||||
await expect(workingDiffLayoutToggle).toHaveAccessibleName("Switch to unified diff");
|
||||
await visiblePanel.getByTestId("working-diff-options-menu").click();
|
||||
await expect(page.getByTestId("working-diff-toggle-whitespace")).toContainText("Hide whitespace");
|
||||
await expect(page.getByTestId("working-diff-toggle-wrap-lines")).toContainText("Wrap long lines");
|
||||
await expect(page.getByTestId("working-diff-refresh")).toContainText("Refresh");
|
||||
await page.getByTestId("working-diff-toggle-wrap-lines").click();
|
||||
await visiblePanel.getByTestId("working-diff-options-menu").click();
|
||||
await expect(page.getByTestId("working-diff-toggle-wrap-lines")).toContainText(
|
||||
"Scroll long lines",
|
||||
);
|
||||
await expect(visiblePanel.getByRole("button", { name: "Switch to unified diff" })).toBeVisible();
|
||||
await visiblePanel.getByRole("button", { name: "Diff options" }).click();
|
||||
await expect(page.getByText("Hide whitespace", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Wrap long lines", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText(/^Refresh/)).toBeVisible();
|
||||
await page.getByText("Wrap long lines", { exact: true }).click();
|
||||
await visiblePanel.getByRole("button", { name: "Diff options" }).click();
|
||||
await expect(page.getByText("Scroll long lines", { exact: true })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await visiblePanel.getByTestId("working-diff-toggle-expand-all").click();
|
||||
await expect(visiblePanel.getByTestId(/^diff-file-\d+-body$/)).toHaveCount(0);
|
||||
await visiblePanel.getByTestId("working-diff-toggle-expand-all").click();
|
||||
await page.getByRole("button", { name: "Expand all files" }).click();
|
||||
await expect(visiblePanel.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
|
||||
await page.getByTestId("explorer-content-area").getByTestId("diff-file-0-toggle").click();
|
||||
await expect(
|
||||
page.getByTestId("explorer-content-area").getByTestId("diff-file-0-body"),
|
||||
).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "Collapse all files" }).click();
|
||||
await expect(visiblePanel.getByTestId(/^diff-file-\d+-body$/)).toHaveCount(0);
|
||||
await expect(page.getByTestId(/^workspace-working-diff-close-/)).toHaveCount(1);
|
||||
|
||||
await writeFile(path.join(workspace.repoPath, "src/use-mounted-tab-set.ts"), BEFORE);
|
||||
@@ -505,24 +516,8 @@ test("Changes switches between inline and full-tab navigation", async ({ page })
|
||||
await expect(visiblePanel.getByText("use-mounted-tab-set.ts", { exact: true })).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await expect(page.getByTestId("explorer-content-area").getByTestId("diff-file-1")).toContainText(
|
||||
"zz-deleted.ts",
|
||||
);
|
||||
await page.getByTestId("explorer-content-area").getByTestId("diff-file-1-toggle").click();
|
||||
await expect(page.getByTestId(/^workspace-working-diff-close-/)).toHaveCount(1);
|
||||
await expect(visiblePanel.getByText("zz-deleted.ts", { exact: true })).toBeVisible();
|
||||
await expect(visiblePanel.getByRole("img", { name: "Deleted" })).toBeVisible();
|
||||
|
||||
await changesTabToggle.click();
|
||||
await expect(page.getByTestId(/^workspace-working-diff-close-/)).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("explorer-content-area").getByTestId("diff-file-0-body"),
|
||||
).toBeVisible();
|
||||
await page.getByTestId("explorer-content-area").getByTestId("diff-file-0-toggle").click();
|
||||
await expect(
|
||||
page.getByTestId("explorer-content-area").getByTestId("diff-file-0-body"),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("changes diff switches between flat and tree file lists", async ({ page }) => {
|
||||
@@ -547,16 +542,18 @@ test("changes diff switches between flat and tree file lists", async ({ page })
|
||||
|
||||
await scrollToLowerUnwrappedDiffRows(page);
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await expect(page.getByTestId("diff-folder-src")).toBeVisible();
|
||||
await expect(page.getByTestId("diff-folder-src").getByText("src", { exact: true })).toHaveCSS(
|
||||
const tree = changesTree(page);
|
||||
const content = changesContent(page);
|
||||
await expect(tree.getByTestId("diff-folder-src")).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-folder-src").getByText("src", { exact: true })).toHaveCSS(
|
||||
"user-select",
|
||||
"none",
|
||||
);
|
||||
await expect(page.getByTestId("diff-file-0")).toBeVisible();
|
||||
await expect(page.getByTestId("diff-folder-src-toggle").locator("svg")).toHaveCount(1);
|
||||
await expect(page.getByTestId("diff-file-0-toggle").locator("svg")).toHaveCount(1);
|
||||
const folderToggleBounds = await page.getByTestId("diff-folder-src-toggle").boundingBox();
|
||||
const folderChevronBounds = await page
|
||||
await expect(tree.getByTestId("diff-file-0")).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-folder-src-toggle").locator("svg")).toHaveCount(1);
|
||||
await expect(tree.getByTestId("diff-file-0-toggle").locator("svg")).toHaveCount(1);
|
||||
const folderToggleBounds = await tree.getByTestId("diff-folder-src-toggle").boundingBox();
|
||||
const folderChevronBounds = await tree
|
||||
.getByTestId("diff-folder-src-toggle")
|
||||
.locator("svg")
|
||||
.boundingBox();
|
||||
@@ -566,11 +563,11 @@ test("changes diff switches between flat and tree file lists", async ({ page })
|
||||
folderToggleBounds!.y + folderToggleBounds!.height / 2,
|
||||
0,
|
||||
);
|
||||
const folderLabelBounds = await page
|
||||
const folderLabelBounds = await tree
|
||||
.getByTestId("diff-folder-src")
|
||||
.getByText("src", { exact: true })
|
||||
.boundingBox();
|
||||
const fileLabelBounds = await page
|
||||
const fileLabelBounds = await tree
|
||||
.getByTestId("diff-file-0")
|
||||
.getByText("use-mounted-tab-set.ts", { exact: true })
|
||||
.boundingBox();
|
||||
@@ -578,28 +575,28 @@ test("changes diff switches between flat and tree file lists", async ({ page })
|
||||
expect(fileLabelBounds).not.toBeNull();
|
||||
expect(fileLabelBounds!.x - folderLabelBounds!.x).toBeCloseTo(16, 0);
|
||||
|
||||
const folderToggle = page.getByTestId("diff-folder-src-toggle");
|
||||
const folderToggle = tree.getByTestId("diff-folder-src-toggle");
|
||||
await folderToggle.click();
|
||||
await expect(folderToggle).toHaveAttribute("aria-selected", "true");
|
||||
await expect(page.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
await expect(tree.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
await folderToggle.click();
|
||||
await expect(folderToggle).toHaveAttribute("aria-selected", "true");
|
||||
await expect(page.getByTestId("diff-file-0")).toBeVisible();
|
||||
await expect(tree.getByTestId("diff-file-0")).toBeVisible();
|
||||
|
||||
const fileToggle = page.getByTestId("diff-file-0-toggle");
|
||||
const fileToggle = tree.getByTestId("diff-file-0-toggle");
|
||||
await fileToggle.click({ button: "right" });
|
||||
await expect(fileToggle).toHaveAttribute("aria-selected", "true");
|
||||
await expect(folderToggle).toHaveAttribute("aria-selected", "false");
|
||||
await expect(page.getByTestId("diff-file-0-context-menu")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByRole("button", { name: "Collapse all" }).click();
|
||||
await expect(page.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "Expand all" }).click();
|
||||
await expect(page.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Collapse all files" }).click();
|
||||
await expect(content.getByTestId(/^diff-file-\d+-body$/)).toHaveCount(0);
|
||||
await page.getByRole("button", { name: "Expand all files" }).click();
|
||||
await expect(content.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
|
||||
await page.getByTestId("diff-folder-src-toggle").click();
|
||||
await expect(page.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
await tree.getByTestId("diff-folder-src-toggle").click();
|
||||
await expect(tree.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await expectFlatFileList(page);
|
||||
@@ -801,14 +798,13 @@ async function openWorkspaceChanges(page: Page, workspace: DirtyWorkspace): Prom
|
||||
await page.setViewportSize({ width: 1400, height: 900 });
|
||||
await page.goto(buildHostWorkspaceRoute(getServerId(), workspace.id));
|
||||
await waitForWorkspaceTabsVisible(page);
|
||||
await page.getByRole("button", { name: "Open explorer" }).click();
|
||||
await openChangesInVisibleExplorer(page);
|
||||
await page.getByTestId("diff-file-0").click();
|
||||
await expectExpandedMountedTabDiff(page);
|
||||
}
|
||||
|
||||
async function openChangesInVisibleExplorer(page: Page): Promise<void> {
|
||||
await expect(page.getByTestId("explorer-tab-changes")).toBeVisible({ timeout: 30_000 });
|
||||
await openChangesPanel(page);
|
||||
await expect(page.getByText("use-mounted-tab-set.ts")).toBeVisible({ timeout: 30_000 });
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { test, expect } from "../support/fixtures";
|
||||
import { openChangesPanel } from "../support/helpers/workspace-tabs";
|
||||
|
||||
const COMMIT_SUBJECT = "Show commit timestamps";
|
||||
|
||||
@@ -13,7 +14,7 @@ test("commit history explains when the workspace has no commits ahead of its bas
|
||||
execFileSync("git", ["checkout", "-b", "feature"], { cwd: workspace.repoPath, stdio: "ignore" });
|
||||
await workspace.navigateTo();
|
||||
|
||||
await page.getByRole("button", { name: "Open explorer" }).click();
|
||||
await openChangesPanel(page);
|
||||
const commitsSection = page.getByRole("button", { name: /Commits/i });
|
||||
await expect(commitsSection).toBeVisible({ timeout: 30_000 });
|
||||
await commitsSection.click();
|
||||
@@ -33,7 +34,7 @@ test("commit history shows dates and shares diff layout preferences", async ({
|
||||
await page.setViewportSize({ width: 1400, height: 900 });
|
||||
await workspace.navigateTo();
|
||||
|
||||
await page.getByRole("button", { name: "Open explorer" }).click();
|
||||
await openChangesPanel(page);
|
||||
const commitsSection = page.getByRole("button", { name: /Commits/i });
|
||||
await expect(commitsSection).toBeVisible({ timeout: 30_000 });
|
||||
await commitsSection.click();
|
||||
@@ -57,6 +58,7 @@ test("commit history shows dates and shares diff layout preferences", async ({
|
||||
await expect(panel.getByTestId("diff-code-row-0")).toHaveCount(0);
|
||||
await expect(panel.getByTestId("diff-file-0-body")).toBeVisible();
|
||||
|
||||
await page.getByTestId(/^workspace-tab-commit_diff_/).hover();
|
||||
await page.getByTestId(/^workspace-commit-diff-close-/).click();
|
||||
await expect(panel).toHaveCount(0);
|
||||
await commitRow.click();
|
||||
|
||||
@@ -38,10 +38,7 @@ function fitsViewportWidth(element: HTMLElement): boolean {
|
||||
}
|
||||
|
||||
async function replaceEditorText(page: Page, content: string): Promise<void> {
|
||||
const contentElement = editor(page);
|
||||
await contentElement.click();
|
||||
await contentElement.press("Control+A");
|
||||
await contentElement.type(content);
|
||||
await editor(page).fill(content);
|
||||
}
|
||||
|
||||
async function openWorkspaceFile(page: Page, filename: string): Promise<void> {
|
||||
@@ -154,9 +151,8 @@ test.describe("CodeMirror workspace file editing", () => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await openAgentRoute(page, session);
|
||||
|
||||
await page.getByRole("button", { name: "Split pane right" }).first().click();
|
||||
await expect(page.getByTestId("workspace-tabs-row").filter({ visible: true })).toHaveCount(2);
|
||||
await openWorkspaceFile(page, "target.ts");
|
||||
await expect(page.getByTestId("workspace-tabs-row").filter({ visible: true })).toHaveCount(2);
|
||||
|
||||
await page
|
||||
.getByTestId(`workspace-tab-agent_${session.agentId}`)
|
||||
@@ -347,14 +343,13 @@ test.describe("CodeMirror workspace file editing", () => {
|
||||
await replaceEditorText(page, "const localWins = 5;\n");
|
||||
await writeFile(sourcePath, "const diskLoses = 6;\n", "utf8");
|
||||
await expect(page.getByTestId("file-conflict-alert")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Overwrite", exact: true }).click();
|
||||
await expect.poll(() => readFile(sourcePath, "utf8")).toBe("const localWins = 5;\n");
|
||||
for (const fileName of ["one.ts", "two.ts", "three.ts", "four.ts"]) {
|
||||
await openWorkspaceFile(page, fileName);
|
||||
}
|
||||
await page.getByTestId("workspace-tab-file_source.ts").filter({ visible: true }).click();
|
||||
await openWorkspaceFile(page, "source.ts");
|
||||
await expect(editor(page)).toContainText("const localWins = 5;");
|
||||
await expect(page.getByTestId("file-conflict-alert")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Overwrite", exact: true }).click();
|
||||
await expect.poll(() => readFile(sourcePath, "utf8")).toBe("const localWins = 5;\n");
|
||||
|
||||
await replaceEditorText(page, "const discarded = 7;\n");
|
||||
await writeFile(sourcePath, "const diskWins = 8;\n", "utf8");
|
||||
@@ -441,20 +436,21 @@ test.describe("CodeMirror workspace file editing", () => {
|
||||
await workspace.navigateTo();
|
||||
await openWorkspaceFile(page, "notes.md");
|
||||
|
||||
await expect(page.getByText("First heading", { exact: true })).toBeVisible();
|
||||
const visibleFilePane = page.getByTestId("workspace-file-pane").filter({ visible: true });
|
||||
await expect(visibleFilePane.getByText("First heading", { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Preview", exact: true })).toBeVisible();
|
||||
await writeFile(markdownPath, "# Updated heading\n", "utf8");
|
||||
await expect(page.getByText("Updated heading", { exact: true })).toBeVisible();
|
||||
await expect(visibleFilePane.getByText("Updated heading", { exact: true })).toBeVisible();
|
||||
|
||||
await selectFileView(page, "Source");
|
||||
await expect(page.getByTestId("file-source-editor")).toBeVisible();
|
||||
await replaceEditorText(page, "# Saved from source\n");
|
||||
await expect.poll(() => readFile(markdownPath, "utf8")).toBe("# Saved from source\n");
|
||||
await selectFileView(page, "Preview");
|
||||
await expect(page.getByText("Saved from source", { exact: true })).toBeVisible();
|
||||
await expect(visibleFilePane.getByText("Saved from source", { exact: true })).toBeVisible();
|
||||
|
||||
await openWorkspaceFile(page, "pixel.png");
|
||||
const image = page.getByTestId("workspace-file-pane").locator("img");
|
||||
const image = visibleFilePane.locator("img");
|
||||
await expect(image).toBeVisible();
|
||||
const initialSource = await image.getAttribute("src");
|
||||
await writeFile(imagePath, BLUE_PIXEL);
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
import { test } from "../support/fixtures";
|
||||
import { openFileExplorer } from "../support/helpers/file-explorer";
|
||||
import { openChangesPanel } from "../support/helpers/workspace-tabs";
|
||||
import { gotoWorkspace } from "../support/helpers/launcher";
|
||||
import { daemonWsRoutePattern } from "../support/helpers/daemon-port";
|
||||
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
||||
@@ -437,7 +438,7 @@ test("hides unsupported file operations and revert actions", async ({ page }) =>
|
||||
await expect(filesMenu.getByText("Delete", { exact: true })).toHaveCount(0);
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await page.getByTestId("explorer-tab-changes").click();
|
||||
await openChangesPanel(page);
|
||||
await expect(page.getByTestId("diff-file-0")).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByTestId("diff-file-0-toggle").click({ button: "right" });
|
||||
await expect(page.getByTestId("diff-file-0-duplicate")).toHaveCount(0);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { test, expect } from "../support/fixtures";
|
||||
import { gotoWorkspace } from "../support/helpers/launcher";
|
||||
import { seedWorkspace } from "../support/helpers/seed-client";
|
||||
import {
|
||||
expectExplorerNodeRetained,
|
||||
hideExplorerSplit,
|
||||
measureExplorerSplit,
|
||||
openExplorerSplit,
|
||||
resizeExplorerSplit,
|
||||
stampExplorerNode,
|
||||
} from "../support/helpers/split-pane";
|
||||
import {
|
||||
createAgentTabFromMenu,
|
||||
waitForWorkspaceTabsVisible,
|
||||
} from "../support/helpers/workspace-tabs";
|
||||
|
||||
test.describe("Hidden split pane retention", () => {
|
||||
test("releases all layout space without unmounting and restores its dragged width", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "hidden-split-pane-" });
|
||||
|
||||
try {
|
||||
await gotoWorkspace(page, workspace.workspaceId);
|
||||
await waitForWorkspaceTabsVisible(page);
|
||||
await createAgentTabFromMenu(page);
|
||||
await openExplorerSplit(page);
|
||||
|
||||
// Drag to a non-default width first: restoring the default would prove nothing.
|
||||
await resizeExplorerSplit(page, 137);
|
||||
await stampExplorerNode(page);
|
||||
const open = await measureExplorerSplit(page);
|
||||
expect(open.explorerWidth).toBeGreaterThan(250);
|
||||
expect(open.separatorCount).toBe(1);
|
||||
|
||||
await hideExplorerSplit(page);
|
||||
const hidden = await measureExplorerSplit(page);
|
||||
expect(hidden.explorerWidth).toBe(0);
|
||||
// The sibling must absorb the whole row, not just collapse the hidden pane.
|
||||
expect(hidden.visibleWidth).toBeCloseTo(hidden.containerWidth, 5);
|
||||
expect(hidden.occupiedWidth).toBeCloseTo(hidden.containerWidth, 5);
|
||||
expect(hidden.separatorCount).toBe(0);
|
||||
// Same DOM node, so a teardown-and-rebuild cannot pass as retention.
|
||||
await expectExplorerNodeRetained(page);
|
||||
|
||||
await openExplorerSplit(page);
|
||||
await expectExplorerNodeRetained(page);
|
||||
const reopened = await measureExplorerSplit(page);
|
||||
expect(reopened.explorerWidth).toBeCloseTo(open.explorerWidth, 5);
|
||||
expect(reopened.separatorCount).toBe(1);
|
||||
|
||||
await testInfo.attach("split-pane-geometry", {
|
||||
body: JSON.stringify({ open, hidden, reopened }, null, 2),
|
||||
contentType: "application/json",
|
||||
});
|
||||
} finally {
|
||||
await workspace.cleanup();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,28 +1,22 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { test, expect, type Page } from "../support/fixtures";
|
||||
import { test, expect } from "../support/fixtures";
|
||||
import { gotoWorkspace, clickNewTerminal } from "../support/helpers/launcher";
|
||||
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
||||
import { expectExplorerEntryVisible } from "../support/helpers/file-explorer";
|
||||
import { expectNoTerminalTabs, clickFirstTerminalTab } from "../support/helpers/workspace-tabs";
|
||||
import { expectExplorerEntryVisible, openFileExplorer } from "../support/helpers/file-explorer";
|
||||
import {
|
||||
expectNoTerminalTabs,
|
||||
clickFirstTerminalTab,
|
||||
openChangesPanel,
|
||||
} from "../support/helpers/workspace-tabs";
|
||||
|
||||
// Model B: two workspaces can back the SAME directory. What follows from that
|
||||
// split is the contract these specs pin:
|
||||
// - The right sidebar (file browser / git changes) reads the directory, so it
|
||||
// is IDENTICAL across same-directory workspaces.
|
||||
// - Files and Changes read the directory, so their content is IDENTICAL
|
||||
// across same-directory workspaces.
|
||||
// - Tabs (agents, terminals) are owned by the workspace, so they are
|
||||
// INDEPENDENT across same-directory workspaces.
|
||||
|
||||
// On desktop the explorer is pinned open; on narrow layouts it must be toggled.
|
||||
// Open it either way, then select the requested tab.
|
||||
async function openExplorerTab(page: Page, tab: "files" | "changes"): Promise<void> {
|
||||
const openButton = page.getByRole("button", { name: "Open explorer" }).first();
|
||||
if (await openButton.isVisible().catch(() => false)) {
|
||||
await openButton.click();
|
||||
}
|
||||
await page.getByTestId(`explorer-tab-${tab}`).click();
|
||||
}
|
||||
|
||||
async function createSecondWorkspaceOnSameDir(
|
||||
seeded: SeededWorkspace,
|
||||
title: string,
|
||||
@@ -41,7 +35,7 @@ async function createSecondWorkspaceOnSameDir(
|
||||
test.describe("Same-directory workspaces", () => {
|
||||
test.describe.configure({ timeout: 180_000 });
|
||||
|
||||
test("the right sidebar is shared: a directory change shows in both same-dir workspaces", async ({
|
||||
test("directory-backed Files and Changes content is shared across same-dir workspaces", async ({
|
||||
page,
|
||||
}) => {
|
||||
const seeded = await seedWorkspace({ repoPrefix: "same-dir-shared-" });
|
||||
@@ -71,19 +65,19 @@ test.describe("Same-directory workspaces", () => {
|
||||
// Workspace A: the new file shows in both the file browser and the git
|
||||
// changes view.
|
||||
await gotoWorkspace(page, seeded.workspaceId);
|
||||
await openExplorerTab(page, "files");
|
||||
await openFileExplorer(page);
|
||||
await expectExplorerEntryVisible(page, "SHARED_CHANGE.md");
|
||||
await openExplorerTab(page, "changes");
|
||||
await openChangesPanel(page);
|
||||
await expect(
|
||||
page.getByTestId("git-diff-scroll").getByText("SHARED_CHANGE.md", { exact: true }).first(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Workspace B (same directory): the SAME change is visible. The right
|
||||
// sidebar content does not differ between the two views.
|
||||
// Workspace B (same directory): the SAME directory-backed content is
|
||||
// visible even though the panel tabs belong to a different workspace.
|
||||
await gotoWorkspace(page, secondWorkspaceId);
|
||||
await openExplorerTab(page, "files");
|
||||
await openFileExplorer(page);
|
||||
await expectExplorerEntryVisible(page, "SHARED_CHANGE.md");
|
||||
await openExplorerTab(page, "changes");
|
||||
await openChangesPanel(page);
|
||||
await expect(
|
||||
page.getByTestId("git-diff-scroll").getByText("SHARED_CHANGE.md", { exact: true }).first(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
@@ -1,26 +1,39 @@
|
||||
import type { Locator } from "@playwright/test";
|
||||
import { expect, test, type Page } from "../support/fixtures";
|
||||
|
||||
test.use({ viewport: { width: 1600, height: 900 } });
|
||||
|
||||
async function expectBorderHighlight(page: Page, testID: string) {
|
||||
async function expectBorderHighlight(
|
||||
page: Page,
|
||||
testID: string,
|
||||
hoverTarget: Locator,
|
||||
expectedWidth: string,
|
||||
) {
|
||||
const handle = page.getByTestId(testID);
|
||||
await expect(handle).toBeVisible();
|
||||
await expect(page.getByTestId(`${testID}-highlight`)).toHaveCount(0);
|
||||
|
||||
await handle.hover();
|
||||
await hoverTarget.hover();
|
||||
|
||||
const highlight = page.getByTestId(`${testID}-highlight`);
|
||||
await expect(highlight).toBeVisible();
|
||||
await expect(highlight).toHaveCSS("width", "1px");
|
||||
await expect(highlight).toHaveCSS("width", expectedWidth);
|
||||
await expect(highlight).not.toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
}
|
||||
|
||||
test("both sidebar borders highlight on hover", async ({ page, withWorkspace }) => {
|
||||
test("sidebar and workspace pane borders highlight on hover", async ({ page, withWorkspace }) => {
|
||||
const workspace = await withWorkspace({ prefix: "sidebar-resize-handle-" });
|
||||
await workspace.navigateTo();
|
||||
|
||||
await expectBorderHighlight(page, "left-sidebar-resize-handle");
|
||||
const sidebarHandle = page.getByTestId("left-sidebar-resize-handle");
|
||||
await expectBorderHighlight(page, "left-sidebar-resize-handle", sidebarHandle, "1px");
|
||||
|
||||
await page.getByTestId("workspace-explorer-toggle").first().click();
|
||||
await expectBorderHighlight(page, "explorer-sidebar-resize-handle");
|
||||
const splitHandle = page.getByTestId("workspace-split-resize-handle");
|
||||
await expectBorderHighlight(
|
||||
page,
|
||||
"workspace-split-resize-handle",
|
||||
splitHandle.getByRole("separator"),
|
||||
"3px",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import { expectWorkspaceHeader } from "../support/helpers/workspace-ui";
|
||||
import { getServerId } from "../support/helpers/server-id";
|
||||
import { projectEquivalenceViewKey } from "../support/helpers/project-view-key";
|
||||
import { escapeRegex } from "../support/helpers/regex";
|
||||
import { openFilesPanel } from "../support/helpers/workspace-tabs";
|
||||
|
||||
const GITHUB_REMOTE_URL = "https://github.com/test-owner/test-repo.git";
|
||||
|
||||
@@ -331,7 +332,7 @@ test.describe("Half-screen desktop layout", () => {
|
||||
await expect(page.getByTestId("sidebar-settings")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("yields app navigation to the Explorer", async ({ page }) => {
|
||||
test("keeps app navigation beside the Explorer pane", async ({ page }) => {
|
||||
const workspace = await seedWorkspace({ repoPrefix: "sidebar-half-screen-explorer-" });
|
||||
|
||||
try {
|
||||
@@ -339,44 +340,19 @@ test.describe("Half-screen desktop layout", () => {
|
||||
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("workspace-explorer-toggle").first()).toBeVisible();
|
||||
await expect(page.getByTestId("explorer-close")).toBeVisible();
|
||||
await expect(page.getByTestId("sidebar-global-new-workspace")).not.toBeVisible();
|
||||
await openFilesPanel(page);
|
||||
const explorerToggle = page.getByTestId("workspace-explorer-toggle").first();
|
||||
await expect(page.getByTestId("workspace-tab-files").filter({ visible: true })).toBeVisible();
|
||||
await expect(explorerToggle).toHaveAccessibleName("Close explorer");
|
||||
await expect(page.getByTestId("sidebar-global-new-workspace")).toBeVisible();
|
||||
await expect(page.getByTestId("workspace-tabs-row").filter({ visible: true })).toHaveCount(2);
|
||||
|
||||
const centerBounds = await page.getByTestId("workspace-tabs-row").first().boundingBox();
|
||||
const headerGlyphBounds = await page
|
||||
.getByTestId("menu-button")
|
||||
.locator("svg")
|
||||
.first()
|
||||
.boundingBox();
|
||||
const tabGlyphBounds = await page
|
||||
.locator('[data-testid^="workspace-tab-"]')
|
||||
.first()
|
||||
.locator("svg")
|
||||
.first()
|
||||
.boundingBox();
|
||||
expect(centerBounds).not.toBeNull();
|
||||
expect(headerGlyphBounds).not.toBeNull();
|
||||
expect(tabGlyphBounds).not.toBeNull();
|
||||
expect((headerGlyphBounds?.x ?? 0) - (centerBounds?.x ?? 0)).toBeCloseTo(
|
||||
(tabGlyphBounds?.x ?? 0) - (centerBounds?.x ?? 0),
|
||||
await explorerToggle.click();
|
||||
await expect(page.getByTestId("workspace-tab-files").filter({ visible: true })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await page.getByTestId("workspace-tabs-row").first().boundingBox())?.width ?? 0,
|
||||
)
|
||||
.toBeGreaterThanOrEqual(400);
|
||||
|
||||
await page.getByTestId("explorer-close").click();
|
||||
await expect(page.getByTestId("explorer-tab-files")).not.toBeVisible();
|
||||
await expect(page.getByTestId("workspace-explorer-toggle").first()).toBeVisible();
|
||||
await expect(explorerToggle).toHaveAccessibleName("Open explorer");
|
||||
await expect(page.getByTestId("sidebar-global-new-workspace")).toBeVisible();
|
||||
} finally {
|
||||
await workspace.cleanup();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { expectComposerVisible, submitMessage } from "../support/helpers/compose
|
||||
import { delayCreatedAgentInitialTailResponse } from "../support/helpers/agent-timeline-gate";
|
||||
import { delayBrowserAgentCreatedStatus } from "../support/helpers/new-workspace";
|
||||
import { seedWorkspace, type SeedDaemonClient } from "../support/helpers/seed-client";
|
||||
import { waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
|
||||
import {
|
||||
createAgentTabFromMenu,
|
||||
waitForWorkspaceTabsVisible,
|
||||
} from "../support/helpers/workspace-tabs";
|
||||
import { getServerId } from "../support/helpers/server-id";
|
||||
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
|
||||
|
||||
@@ -56,7 +59,7 @@ test.describe("Workspace agent title handoff", () => {
|
||||
try {
|
||||
await page.goto(buildHostWorkspaceRoute(getServerId(), workspace.workspaceId));
|
||||
await waitForWorkspaceTabsVisible(page);
|
||||
await page.getByTestId("workspace-new-agent-tab-inline").click();
|
||||
await createAgentTabFromMenu(page);
|
||||
await expectComposerVisible(page);
|
||||
|
||||
const prompt = "Keep the optimistic agent pane visible during handoff";
|
||||
@@ -99,7 +102,7 @@ test.describe("Workspace agent title handoff", () => {
|
||||
try {
|
||||
await page.goto(buildHostWorkspaceRoute(getServerId(), workspace.workspaceId));
|
||||
await waitForWorkspaceTabsVisible(page);
|
||||
await page.getByTestId("workspace-new-agent-tab-inline").click();
|
||||
await createAgentTabFromMenu(page);
|
||||
await expectComposerVisible(page);
|
||||
|
||||
const promptTitle = "Investigate optimistic tab title handoff";
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { seedWorkspace, type SeededWorkspace } from "../support/helpers/seed-client";
|
||||
import { expectExplorerEntryVisible } from "../support/helpers/file-explorer";
|
||||
import { getServerId } from "../support/helpers/server-id";
|
||||
import { openFilesPanel } from "../support/helpers/workspace-tabs";
|
||||
import { projectEquivalenceViewKey } from "../support/helpers/project-view-key";
|
||||
import { waitForSidebarHydration } from "../support/helpers/workspace-ui";
|
||||
|
||||
@@ -24,14 +25,8 @@ function workspaceRowTestId(workspaceId: string): string {
|
||||
return `sidebar-workspace-row-${getServerId()}:${workspaceId}`;
|
||||
}
|
||||
|
||||
// On desktop the file explorer is pinned open; on narrower layouts it must be
|
||||
// toggled first. Open it either way, then select the Files tab.
|
||||
async function openFilesTab(page: Page): Promise<void> {
|
||||
const openButton = page.getByRole("button", { name: "Open explorer" }).first();
|
||||
if (await openButton.isVisible().catch(() => false)) {
|
||||
await openButton.click();
|
||||
}
|
||||
await page.getByTestId("explorer-tab-files").click();
|
||||
await openFilesPanel(page);
|
||||
await expect(page.getByTestId("file-explorer-tree-scroll")).toBeVisible({ timeout: 30_000 });
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,12 @@ async function getVisibleDraftTabCount(page: Page): Promise<number> {
|
||||
}
|
||||
|
||||
async function closeFirstVisibleDraftTab(page: Page): Promise<void> {
|
||||
const tab = page
|
||||
.locator('[data-testid^="workspace-tab-draft"]')
|
||||
.filter({ visible: true })
|
||||
.first();
|
||||
await expect(tab).toBeVisible({ timeout: 30_000 });
|
||||
await tab.hover();
|
||||
const closeButton = page.locator('[data-testid^="workspace-draft-close-"]').filter({
|
||||
visible: true,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user