mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Update OrgSelector to use Dropdown component and fix related tests
This commit is contained in:
parent
d8feafee69
commit
f4fdcae6e5
@ -31,8 +31,9 @@ describe("OrgSelector", () => {
|
||||
|
||||
renderOrgSelector();
|
||||
|
||||
const selector = screen.getByTestId("org-selector");
|
||||
expect(selector).toBeDisabled();
|
||||
// The dropdown trigger should be disabled while loading
|
||||
const trigger = screen.getByTestId("dropdown-trigger");
|
||||
expect(trigger).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should select the first organization after orgs are loaded", async () => {
|
||||
@ -43,13 +44,14 @@ describe("OrgSelector", () => {
|
||||
|
||||
renderOrgSelector();
|
||||
|
||||
// The combobox input should show the first org name
|
||||
await waitFor(() => {
|
||||
const selector = screen.getByTestId("org-selector");
|
||||
expect(selector).toHaveValue("Personal Workspace");
|
||||
const input = screen.getByRole("combobox");
|
||||
expect(input).toHaveValue("Personal Workspace");
|
||||
});
|
||||
});
|
||||
|
||||
it("should show all options when the clear button is pressed", async () => {
|
||||
it("should show all options when dropdown is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
vi.spyOn(organizationService, "getOrganizations").mockResolvedValue([
|
||||
MOCK_PERSONAL_ORG,
|
||||
@ -60,13 +62,14 @@ describe("OrgSelector", () => {
|
||||
renderOrgSelector();
|
||||
|
||||
// Wait for the selector to be populated with the first organization
|
||||
const selector = await screen.findByTestId("org-selector");
|
||||
await waitFor(() => {
|
||||
expect(selector).toHaveValue("Personal Workspace");
|
||||
const input = screen.getByRole("combobox");
|
||||
expect(input).toHaveValue("Personal Workspace");
|
||||
});
|
||||
|
||||
// Click to open dropdown
|
||||
await user.click(selector);
|
||||
// Click the trigger to open dropdown
|
||||
const trigger = screen.getByTestId("dropdown-trigger");
|
||||
await user.click(trigger);
|
||||
|
||||
// Verify all 3 options are visible
|
||||
const listbox = await screen.findByRole("listbox");
|
||||
|
||||
@ -54,7 +54,8 @@ vi.mock("react-router", async (importActual) => ({
|
||||
|
||||
describe("UserContextMenu", () => {
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
navigateMock.mockClear();
|
||||
});
|
||||
|
||||
it("should render the default context items for a user", () => {
|
||||
@ -317,12 +318,16 @@ describe("UserContextMenu", () => {
|
||||
|
||||
renderUserContextMenu({ type: "admin", onClose: vi.fn });
|
||||
|
||||
// Wait for orgs to load, then verify buttons are hidden for personal org
|
||||
// Wait for orgs to load AND org to be selected (buttons should disappear)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toHaveValue(
|
||||
MOCK_PERSONAL_ORG.name,
|
||||
);
|
||||
expect(
|
||||
screen.queryByText("ORG$MANAGE_ORGANIZATION_MEMBERS"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByText("ORG$MANAGE_ACCOUNT")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -339,8 +344,11 @@ describe("UserContextMenu", () => {
|
||||
|
||||
renderUserContextMenu({ type: "admin", onClose: vi.fn });
|
||||
|
||||
// Wait for orgs to load, then verify Billing is hidden for team orgs
|
||||
// Wait for orgs to load AND org to be selected (Billing should disappear)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toHaveValue(
|
||||
MOCK_TEAM_ORG_ACME.name,
|
||||
);
|
||||
expect(
|
||||
screen.queryByText("SETTINGS$NAV_BILLING"),
|
||||
).not.toBeInTheDocument();
|
||||
@ -367,21 +375,33 @@ describe("UserContextMenu", () => {
|
||||
});
|
||||
|
||||
test("the user can change orgs", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onCloseMock = vi.fn();
|
||||
renderUserContextMenu({ type: "user", onClose: onCloseMock });
|
||||
|
||||
const orgSelector = screen.getByTestId("org-selector");
|
||||
expect(orgSelector).toBeInTheDocument();
|
||||
|
||||
// Simulate changing the organization
|
||||
await userEvent.click(orgSelector);
|
||||
const orgOption = screen.getByText(INITIAL_MOCK_ORGS[1].name);
|
||||
await userEvent.click(orgOption);
|
||||
// Wait for organizations to load (indicated by org name appearing in the dropdown)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toHaveValue(
|
||||
INITIAL_MOCK_ORGS[0].name,
|
||||
);
|
||||
});
|
||||
|
||||
// Open the dropdown by clicking the trigger
|
||||
const trigger = screen.getByTestId("dropdown-trigger");
|
||||
await user.click(trigger);
|
||||
|
||||
// Select a different organization
|
||||
const orgOption = screen.getByRole("option", {
|
||||
name: INITIAL_MOCK_ORGS[1].name,
|
||||
});
|
||||
await user.click(orgOption);
|
||||
|
||||
expect(onCloseMock).not.toHaveBeenCalled();
|
||||
|
||||
// Verify that the dropdown shows the selected organization
|
||||
// The dropdown should now display the selected org name
|
||||
expect(orgSelector).toHaveValue(INITIAL_MOCK_ORGS[1].name);
|
||||
expect(screen.getByRole("combobox")).toHaveValue(INITIAL_MOCK_ORGS[1].name);
|
||||
});
|
||||
});
|
||||
|
||||
@ -352,4 +352,12 @@ describe("Dropdown", () => {
|
||||
it.todo("should call onChange when selection changes");
|
||||
it.todo("should support defaultValue prop");
|
||||
});
|
||||
|
||||
describe("testId prop", () => {
|
||||
it("should apply custom testId to the root container", () => {
|
||||
render(<Dropdown options={mockOptions} testId="org-dropdown" />);
|
||||
|
||||
expect(screen.getByTestId("org-dropdown")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -119,6 +119,7 @@ describe("Manage Org Route", () => {
|
||||
|
||||
it("should render account details", async () => {
|
||||
renderManageOrg();
|
||||
await screen.findByTestId("manage-org-screen");
|
||||
|
||||
await selectOrganization({ orgIndex: 0 });
|
||||
|
||||
|
||||
@ -184,6 +184,7 @@ describe("Manage Organization Members Route", () => {
|
||||
// Helper function to setup invite test (render and select organization)
|
||||
const setupInviteTest = async (orgIndex: number = 0) => {
|
||||
renderManageOrganizationMembers();
|
||||
await screen.findByTestId("manage-organization-members-settings");
|
||||
await selectOrganization({ orgIndex });
|
||||
};
|
||||
|
||||
|
||||
@ -1,34 +1,49 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SettingsDropdownInput } from "../settings/settings-dropdown-input";
|
||||
import { useSelectedOrganizationId } from "#/context/use-selected-organization";
|
||||
import { useOrganizations } from "#/hooks/query/use-organizations";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Dropdown } from "#/ui/dropdown/dropdown";
|
||||
|
||||
export function OrgSelector() {
|
||||
const { t } = useTranslation();
|
||||
const { orgId, setOrgId } = useSelectedOrganizationId();
|
||||
const { data: organizations, isLoading } = useOrganizations();
|
||||
|
||||
// Auto-select the first organization when data loads and no org is selected
|
||||
React.useEffect(() => {
|
||||
if (!orgId && organizations?.length) {
|
||||
if (!orgId && organizations && organizations.length > 0) {
|
||||
setOrgId(organizations[0].id);
|
||||
}
|
||||
}, [orgId, organizations, setOrgId]);
|
||||
|
||||
const selectedOrg = React.useMemo(() => {
|
||||
if (orgId) {
|
||||
return organizations?.find((org) => org.id === orgId);
|
||||
}
|
||||
|
||||
return organizations?.[0];
|
||||
}, [orgId, organizations]);
|
||||
|
||||
return (
|
||||
<SettingsDropdownInput
|
||||
<Dropdown
|
||||
testId="org-selector"
|
||||
name="organization"
|
||||
placeholder={t(I18nKey.ORG$SELECT_ORGANIZATION_PLACEHOLDER)}
|
||||
selectedKey={orgId || ""}
|
||||
isLoading={isLoading}
|
||||
items={
|
||||
organizations?.map((org) => ({ key: org.id, label: org.name })) || []
|
||||
}
|
||||
onSelectionChange={(org) => {
|
||||
setOrgId(org ? org.toString() : null);
|
||||
key={selectedOrg?.id}
|
||||
defaultValue={{
|
||||
label: selectedOrg?.name || "",
|
||||
value: selectedOrg?.id || "",
|
||||
}}
|
||||
onChange={(item) => {
|
||||
setOrgId(item ? item.value : null);
|
||||
}}
|
||||
placeholder={t(I18nKey.ORG$SELECT_ORGANIZATION_PLACEHOLDER)}
|
||||
loading={isLoading}
|
||||
options={
|
||||
organizations?.map((org) => ({
|
||||
value: org.id,
|
||||
label: org.name,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ interface DropdownProps {
|
||||
placeholder?: string;
|
||||
defaultValue?: DropdownOption;
|
||||
onChange?: (item: DropdownOption | null) => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function Dropdown({
|
||||
@ -28,6 +29,7 @@ export function Dropdown({
|
||||
placeholder,
|
||||
defaultValue,
|
||||
onChange,
|
||||
testId,
|
||||
}: DropdownProps) {
|
||||
const [inputValue, setInputValue] = useState(defaultValue?.label ?? "");
|
||||
|
||||
@ -69,7 +71,7 @@ export function Dropdown({
|
||||
const isDisabled = loading || disabled;
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<div className="relative w-full" data-testid={testId}>
|
||||
<div
|
||||
className={cn(
|
||||
"bg-tertiary border border-[#717888] rounded w-full p-2",
|
||||
|
||||
@ -88,31 +88,39 @@ export const selectOrganization = async ({
|
||||
expect.fail(`No organization found at index ${orgIndex}`);
|
||||
}
|
||||
|
||||
// Wait for the settings navbar to render (which contains the org selector)
|
||||
await screen.findByTestId("settings-navbar");
|
||||
|
||||
// Wait for orgs to load and auto-select to happen (first org gets auto-selected)
|
||||
const organizationSelect = await screen.findByTestId("org-selector");
|
||||
expect(organizationSelect).toBeInTheDocument();
|
||||
|
||||
// Wait until the selector is not loading anymore
|
||||
// Wait until the dropdown trigger is not disabled (orgs have loaded)
|
||||
const trigger = await screen.findByTestId("dropdown-trigger");
|
||||
await waitFor(() => {
|
||||
expect(organizationSelect).not.toBeDisabled();
|
||||
expect(trigger).not.toBeDisabled();
|
||||
});
|
||||
|
||||
// Get the combobox input (where the value is displayed)
|
||||
const combobox = screen.getByRole("combobox");
|
||||
|
||||
// If selecting org index 0, it should already be auto-selected
|
||||
if (orgIndex === 0) {
|
||||
await waitFor(() => {
|
||||
expect(organizationSelect).toHaveValue(targetOrg.name);
|
||||
expect(combobox).toHaveValue(targetOrg.name);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await userEvent.click(organizationSelect);
|
||||
// Click the trigger to open the dropdown
|
||||
await userEvent.click(trigger);
|
||||
|
||||
// Find the option by its text content (organization name)
|
||||
const option = await screen.findByText(targetOrg.name);
|
||||
// Find the option by its role and text content (organization name)
|
||||
const option = await screen.findByRole("option", { name: targetOrg.name });
|
||||
await userEvent.click(option);
|
||||
|
||||
// Wait for the selection to be reflected in the input
|
||||
// Wait for the selection to be reflected in the combobox
|
||||
await waitFor(() => {
|
||||
expect(organizationSelect).toHaveValue(targetOrg.name);
|
||||
expect(combobox).toHaveValue(targetOrg.name);
|
||||
});
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user