mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
fix(plugins): a branch and metadata chosen for one project are not sent for another
The setup model outlives the project and host it was chosen for, so an issue picked for project A was sent on workspace.create after the user switched to project B. The model now drops the branch and metadata when the project changes (project ids are host-scoped, so a host switch counts). A project that is still resolving is not a switch. The base selection is left alone: the built-in picker keeps a branch pick across a project switch, and that behavior is unchanged.
This commit is contained in:
@@ -48,6 +48,22 @@ test("a plugin's branch applies when the project has no default base to branch o
|
||||
);
|
||||
});
|
||||
|
||||
test("a branch and metadata chosen for one project are not sent for another", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await withNewWorkspaceIssuesExample(
|
||||
page,
|
||||
{ compact: false, otherProject: true },
|
||||
async (screen) => {
|
||||
await screen.chooseWorktree();
|
||||
await screen.chooseIssue("PAS-101 Slow sidebar");
|
||||
await screen.switchToOtherProject();
|
||||
const created = await screen.createWorkspace();
|
||||
expect(created.branch).not.toBe("pas-101-slow-sidebar");
|
||||
expect(JSON.stringify(created.metadata ?? {})).not.toContain("PAS-101");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("an isolation set from a mount effect is remembered like a pick", async ({ page }) => {
|
||||
test.setTimeout(180_000);
|
||||
await withNewWorkspaceIssuesExample(
|
||||
|
||||
@@ -68,11 +68,18 @@ const COMPACT = { width: 500, height: 800 };
|
||||
/** Runs `run` on the New workspace screen with the new-workspace-issues example installed. */
|
||||
export async function withNewWorkspaceIssuesExample(
|
||||
page: Page,
|
||||
options: { compact: boolean; detachedHead?: boolean; setsOnMount?: boolean },
|
||||
options: {
|
||||
compact: boolean;
|
||||
detachedHead?: boolean;
|
||||
setsOnMount?: boolean;
|
||||
/** A second project the screen can switch to. */
|
||||
otherProject?: boolean;
|
||||
},
|
||||
run: (screen: {
|
||||
chooseWorktree(): Promise<void>;
|
||||
chooseIssue(label: string): Promise<void>;
|
||||
createWorkspace(options?: { prompt?: string }): Promise<{ branch: string; metadata: unknown }>;
|
||||
switchToOtherProject(): Promise<void>;
|
||||
expectIsolation(isolation: "local" | "worktree"): Promise<void>;
|
||||
/** Removes the plugin, leaves the screen, and opens New workspace for the project again. */
|
||||
reopenWithoutPlugin(): Promise<void>;
|
||||
@@ -83,6 +90,9 @@ export async function withNewWorkspaceIssuesExample(
|
||||
const example = await copyPluginExample("new-workspace-issues");
|
||||
if (options.setsOnMount) await replaceWithMountEffectItem(example.directory);
|
||||
const repo = await createTempGitRepo("new-workspace-issues-", { branches: ["main"] });
|
||||
const otherRepo = options.otherProject
|
||||
? await createTempGitRepo("new-workspace-other-", { branches: ["main"] })
|
||||
: null;
|
||||
let createdDirectory: string | null = null;
|
||||
try {
|
||||
// A detached HEAD leaves the screen with no default base to branch off.
|
||||
@@ -90,6 +100,8 @@ export async function withNewWorkspaceIssuesExample(
|
||||
await client.patchDaemonConfig({ pluginsEnabled: true });
|
||||
await client.installDirectoryPlugin(example.directory);
|
||||
const project = await openProjectViaDaemon(client, repo.path);
|
||||
const other = otherRepo ? await openProjectViaDaemon(client, otherRepo.path) : null;
|
||||
let target = project;
|
||||
await gotoAppShell(page);
|
||||
await waitForSidebarHydration(page);
|
||||
await openNewWorkspaceComposer(page, project);
|
||||
@@ -106,6 +118,20 @@ export async function withNewWorkspaceIssuesExample(
|
||||
await page.getByRole("button", { name: "Issue", exact: true }).click();
|
||||
await page.getByText(label, { exact: true }).click();
|
||||
}),
|
||||
switchToOtherProject: () =>
|
||||
test.step("switch to the other project", async () => {
|
||||
if (!other) throw new Error("Open the example with otherProject to switch projects");
|
||||
await page.getByRole("button", { name: "Workspace project" }).click();
|
||||
await page
|
||||
.getByTestId("combobox-desktop-container")
|
||||
.last()
|
||||
.getByText(other.projectDisplayName, { exact: true })
|
||||
.click();
|
||||
await expect(page.getByRole("button", { name: "Workspace project" })).toContainText(
|
||||
other.projectDisplayName,
|
||||
);
|
||||
target = other;
|
||||
}),
|
||||
expectIsolation: (isolation) =>
|
||||
test.step(`the screen is on ${isolation}`, () =>
|
||||
expectWorkspaceIsolationSelected(page, isolation)),
|
||||
@@ -124,8 +150,8 @@ export async function withNewWorkspaceIssuesExample(
|
||||
const created = await assertNewWorkspaceSidebarAndHeader(page, {
|
||||
serverId: getServerId(),
|
||||
client,
|
||||
previousWorkspaceId: project.workspaceId,
|
||||
projectDisplayName: project.projectDisplayName,
|
||||
previousWorkspaceId: target.workspaceId,
|
||||
projectDisplayName: target.projectDisplayName,
|
||||
// The sidebar is closed at compact width.
|
||||
assertSidebarRow: !options.compact,
|
||||
});
|
||||
@@ -142,5 +168,6 @@ export async function withNewWorkspaceIssuesExample(
|
||||
await client.close();
|
||||
await example.cleanup();
|
||||
await repo.cleanup();
|
||||
await otherRepo?.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,19 @@ describe("New workspace setup commands", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("drops the branch and metadata chosen for another project, but not while the project resolves", async () => {
|
||||
const setup = createNewWorkspaceSetup({ ...READY, project: null }, recordingEffects().effects);
|
||||
await setup.commands.setBranch("pas-101");
|
||||
setup.syncInputs(READY);
|
||||
expect(setup.getSnapshot().branch).toBe("pas-101");
|
||||
|
||||
await setup.commands.setMetadata({ issue: "PAS-101" });
|
||||
setup.syncInputs({ ...READY, project: { id: "p2", name: "other" } });
|
||||
|
||||
expect(setup.getSnapshot().branch).toBeNull();
|
||||
expect(setup.getSnapshot().metadata).toEqual({});
|
||||
});
|
||||
|
||||
it("rejects every command while the screen is locked, as the pickers do", async () => {
|
||||
const setup = createNewWorkspaceSetup({ ...READY, locked: true }, recordingEffects().effects);
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ export function createNewWorkspaceSetup(
|
||||
metadata: {},
|
||||
};
|
||||
let inputs = initial;
|
||||
let target = initial.project?.id ?? null;
|
||||
const listeners = new Set<() => void>();
|
||||
const update = (patch: Partial<SetupState>) => {
|
||||
state = { ...state, ...patch };
|
||||
@@ -110,6 +111,12 @@ export function createNewWorkspaceSetup(
|
||||
chooseIsolation,
|
||||
syncInputs: (next) => {
|
||||
inputs = next;
|
||||
// A branch and metadata are chosen for one project on one host. A project id is
|
||||
// host-scoped, so a change of host changes it too. An absent project is a screen that is
|
||||
// still resolving, not a switch.
|
||||
if (!next.project) return;
|
||||
if (target && target !== next.project.id) update({ branch: null, metadata: {} });
|
||||
target = next.project.id;
|
||||
},
|
||||
commands: {
|
||||
async setIsolation(isolation) {
|
||||
|
||||
Reference in New Issue
Block a user