* 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
60 lines
2.7 KiB
TypeScript
60 lines
2.7 KiB
TypeScript
import { expect, type Page } from "@playwright/test";
|
|
import { escapeRegex } from "./regex";
|
|
import { openChangesPanel as openWorkspaceChangesPanel } from "./workspace-tabs";
|
|
|
|
// The branch switcher lives in the Changes tab, not in the workspace header. It renders as a button whose
|
|
// accessible name carries the current branch ("Current branch: <name>. Press to
|
|
// switch branch."). Matching on the accessible name keeps these helpers tied to
|
|
// what a screen reader user hears, and it proves the panel resolved a real
|
|
// checkout directory from the opaque workspace id. Scoping to the changes header
|
|
// keeps the matcher unambiguous even when the header is shared with diff actions.
|
|
function branchSwitcherTrigger(page: Page, branchName: string) {
|
|
return page
|
|
.getByTestId("changes-header")
|
|
.getByRole("button", { name: new RegExp(`Current branch: ${escapeRegex(branchName)}\\b`) })
|
|
.filter({ visible: true })
|
|
.first();
|
|
}
|
|
|
|
// Opens the fixed pane and lands on the Changes tab, where the switcher and diff live.
|
|
export async function openChangesPanel(page: Page): Promise<void> {
|
|
await openWorkspaceChangesPanel(page);
|
|
await expect(page.getByTestId("changes-header").filter({ visible: true }).first()).toBeVisible({
|
|
timeout: 30_000,
|
|
});
|
|
}
|
|
|
|
export async function expectWorkspaceBranch(page: Page, branchName: string): Promise<void> {
|
|
await expect(branchSwitcherTrigger(page, branchName)).toBeVisible({ timeout: 30_000 });
|
|
}
|
|
|
|
export async function switchBranchFromChangesPanel(
|
|
page: Page,
|
|
input: { from: string; to: string },
|
|
): Promise<void> {
|
|
await branchSwitcherTrigger(page, input.from).click();
|
|
|
|
const picker = page.getByTestId("combobox-desktop-container");
|
|
await expect(picker).toBeVisible({ timeout: 30_000 });
|
|
|
|
// The branch switcher combobox renders its options as plain text rows with no ARIA
|
|
// role, so filter by the visible branch name and click the matching row. Filtering
|
|
// first guarantees a single, unambiguous match.
|
|
const search = page.getByPlaceholder("Filter branches...");
|
|
await expect(search).toBeVisible({ timeout: 30_000 });
|
|
await search.fill(input.to);
|
|
|
|
const option = picker.getByText(input.to, { exact: true });
|
|
await expect(option).toBeVisible({ timeout: 30_000 });
|
|
await option.click();
|
|
|
|
await expect(picker).not.toBeVisible({ timeout: 30_000 });
|
|
}
|
|
|
|
// The workspace header title is a plain static title in Model B; the branch
|
|
// switcher must never appear there. Asserting on the header testID keeps this
|
|
// honest even as the switcher continues to exist inside the Changes panel.
|
|
export async function expectNoBranchSwitcherInWorkspaceHeader(page: Page): Promise<void> {
|
|
await expect(page.getByTestId("workspace-header-branch-switcher")).toHaveCount(0);
|
|
}
|