168 lines
5.0 KiB
TypeScript
168 lines
5.0 KiB
TypeScript
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { renderWithProviders } from "../test/renderWithProviders";
|
|
import WorkspaceSelector from "./WorkspaceSelector";
|
|
|
|
function makeWorkspace(name: string, overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: `ws-${name}`,
|
|
name,
|
|
slug: name,
|
|
path: `/workspaces/${name}`,
|
|
runtime_backend: "container",
|
|
container_name: `codex-${name}`,
|
|
status: "active",
|
|
provision_state: "ready",
|
|
provision_error: "",
|
|
last_provisioned_at: "",
|
|
container_state: "running",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("WorkspaceSelector", () => {
|
|
it("shows a loading label while workspaces are being resolved", () => {
|
|
renderWithProviders(
|
|
<WorkspaceSelector value="" workspaces={[]} loading onChange={vi.fn()} />,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
expect(screen.getByRole("button", { name: /loading workspaces/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it("lets the user switch to another existing workspace", async () => {
|
|
const onChange = vi.fn();
|
|
const user = userEvent.setup();
|
|
|
|
renderWithProviders(
|
|
<WorkspaceSelector
|
|
value="alpha"
|
|
workspaces={[makeWorkspace("alpha"), makeWorkspace("beta")]}
|
|
onChange={onChange}
|
|
/>,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
await user.click(await screen.findByRole("button", { name: /alpha/i }));
|
|
|
|
const betaOption = (await screen.findByText("beta")).closest("button");
|
|
if (!betaOption) {
|
|
throw new Error("beta workspace option not found");
|
|
}
|
|
|
|
await user.click(betaOption);
|
|
|
|
expect(onChange).toHaveBeenCalledWith("beta");
|
|
});
|
|
|
|
it("updates the trigger label when the active workspace prop changes", async () => {
|
|
const { rerender } = renderWithProviders(
|
|
<WorkspaceSelector
|
|
value="alpha"
|
|
workspaces={[makeWorkspace("alpha"), makeWorkspace("beta")]}
|
|
onChange={vi.fn()}
|
|
/>,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
expect(await screen.findByRole("button", { name: /alpha/i })).toBeInTheDocument();
|
|
|
|
rerender(
|
|
<WorkspaceSelector
|
|
value="beta"
|
|
workspaces={[makeWorkspace("alpha"), makeWorkspace("beta")]}
|
|
onChange={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(await screen.findByRole("button", { name: /beta/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it("opens a labeled dialog, focuses the active project, and restores focus on escape", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
renderWithProviders(
|
|
<WorkspaceSelector
|
|
value="alpha"
|
|
workspaces={[makeWorkspace("alpha"), makeWorkspace("beta")]}
|
|
onChange={vi.fn()}
|
|
/>,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
const trigger = await screen.findByRole("button", { name: /alpha/i });
|
|
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
|
|
await user.click(trigger);
|
|
expect(trigger).toHaveAttribute("aria-expanded", "true");
|
|
|
|
const dialog = await screen.findByRole("dialog", { name: "Workspaces" });
|
|
await waitFor(() => {
|
|
expect(within(dialog).getByRole("button", { name: /alpha/i })).toHaveFocus();
|
|
});
|
|
|
|
for (let i = 0; i < 4; i += 1) {
|
|
await user.tab();
|
|
expect(dialog.contains(document.activeElement)).toBe(true);
|
|
}
|
|
|
|
await user.keyboard("{Escape}");
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
await waitFor(() => {
|
|
expect(trigger).toHaveFocus();
|
|
});
|
|
expect(trigger).toHaveAttribute("aria-expanded", "false");
|
|
});
|
|
|
|
it("keeps the current workspace selected when the next workspace has no id", async () => {
|
|
const onChange = vi.fn();
|
|
const user = userEvent.setup();
|
|
|
|
renderWithProviders(
|
|
<WorkspaceSelector
|
|
value="alpha"
|
|
workspaces={[
|
|
makeWorkspace("alpha"),
|
|
makeWorkspace("beta", {
|
|
id: "",
|
|
provision_state: "failed",
|
|
container_state: "missing",
|
|
}),
|
|
]}
|
|
onChange={onChange}
|
|
/>,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
await user.click(await screen.findByRole("button", { name: /alpha/i }));
|
|
|
|
const betaOption = (await screen.findByText("beta")).closest("button");
|
|
if (!betaOption) {
|
|
throw new Error("beta workspace option not found");
|
|
}
|
|
|
|
await user.click(betaOption);
|
|
|
|
expect(await screen.findByText("Couldn't prepare the workspace runtime.")).toBeInTheDocument();
|
|
expect(onChange).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("shows a header-managed empty state when no workspaces exist", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
renderWithProviders(
|
|
<WorkspaceSelector value="" workspaces={[]} onChange={vi.fn()} />,
|
|
{ locale: "en" },
|
|
);
|
|
|
|
await user.click(await screen.findByRole("button", { name: /no workspaces/i }));
|
|
|
|
expect(await screen.findByText("No workspaces yet")).toBeInTheDocument();
|
|
expect(screen.getByText("Workspaces must be created outside the header picker.")).toBeInTheDocument();
|
|
});
|
|
});
|