Update OrgSelector to use Dropdown component and fix related tests

This commit is contained in:
amanape 2025-12-19 18:58:06 +04:00
parent d8feafee69
commit f4fdcae6e5
8 changed files with 97 additions and 39 deletions

View File

@ -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");

View File

@ -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);
});
});

View File

@ -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();
});
});
});

View File

@ -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 });

View File

@ -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 });
};

View File

@ -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,
})) || []
}
/>
);
}

View File

@ -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",

View File

@ -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);
});
};