Files
prowler/ui/app/(prowler)/_overview/_components/accounts-selector.test.tsx
T
Pablo Fernandez Guerra (PFE) 5b9824c379 feat(ui): filter by provider group across main views (#11659)
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:32:00 +02:00

285 lines
8.0 KiB
TypeScript

import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { FILTER_FIELD } from "@/types/filters";
import { AccountsSelector } from "./accounts-selector";
const multiSelectContentSpy = vi.fn();
const multiSelectSpy = vi.fn();
vi.mock("next/navigation", () => ({
useSearchParams: () => new URLSearchParams(),
}));
vi.mock("@/hooks/use-url-filters", () => ({
useUrlFilters: () => ({
navigateWithParams: vi.fn(),
}),
}));
vi.mock("@/components/icons/providers-badge", () => ({
AWSProviderBadge: () => <span>AWS</span>,
AzureProviderBadge: () => <span>Azure</span>,
GCPProviderBadge: () => <span>GCP</span>,
CloudflareProviderBadge: () => <span>Cloudflare</span>,
GitHubProviderBadge: () => <span>GitHub</span>,
GoogleWorkspaceProviderBadge: () => <span>Google Workspace</span>,
IacProviderBadge: () => <span>IaC</span>,
ImageProviderBadge: () => <span>Image</span>,
KS8ProviderBadge: () => <span>Kubernetes</span>,
M365ProviderBadge: () => <span>M365</span>,
MongoDBAtlasProviderBadge: () => <span>MongoDB Atlas</span>,
OpenStackProviderBadge: () => <span>OpenStack</span>,
OracleCloudProviderBadge: () => <span>Oracle Cloud</span>,
AlibabaCloudProviderBadge: () => <span>Alibaba Cloud</span>,
VercelProviderBadge: () => <span>Vercel</span>,
OktaProviderBadge: () => <span>Okta</span>,
}));
vi.mock("@/components/shadcn/select/multiselect", () => ({
MultiSelect: ({
children,
open,
onOpenChange,
}: {
children: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}) => {
multiSelectSpy({ open });
return (
<div data-open={String(open)}>
<button type="button" onClick={() => onOpenChange?.(true)}>
Open selector
</button>
{children}
</div>
);
},
MultiSelectTrigger: ({ children }: { children: React.ReactNode }) => (
<div data-testid="trigger">{children}</div>
),
MultiSelectValue: ({ placeholder }: { placeholder: string }) => (
<span>{placeholder}</span>
),
MultiSelectContent: ({
children,
search,
}: {
children: React.ReactNode;
search?: unknown;
}) => {
multiSelectContentSpy(search);
return <div>{children}</div>;
},
MultiSelectItem: ({
children,
disabled,
value,
keywords,
onSelect,
}: {
children: React.ReactNode;
disabled?: boolean;
value: string;
keywords?: string[];
onSelect?: (value: string) => void;
}) => (
<button
type="button"
data-value={value}
data-keywords={keywords?.join("|")}
data-disabled={disabled ? "true" : "false"}
onClick={() => onSelect?.(value)}
>
{children}
</button>
),
}));
const providers = [
{
id: "provider-1",
type: "providers" as const,
attributes: {
provider: "aws" as const,
uid: "123456789012",
alias: "Production AWS",
status: "completed" as const,
resources: 0,
connection: {
connected: true,
last_checked_at: "2026-04-13T00:00:00Z",
},
scanner_args: {
only_logs: false,
excluded_checks: [],
aws_retries_max_attempts: 3,
},
inserted_at: "2026-04-13T00:00:00Z",
updated_at: "2026-04-13T00:00:00Z",
created_by: {
object: "user",
id: "user-1",
},
},
relationships: {
secret: {
data: null,
},
provider_groups: {
meta: {
count: 0,
},
data: [],
},
},
},
];
describe("AccountsSelector", () => {
it("passes searchable dropdown defaults to MultiSelectContent", () => {
render(<AccountsSelector providers={providers} />);
expect(multiSelectContentSpy).toHaveBeenCalledWith({
placeholder: "Search Providers...",
emptyMessage: "No Providers found.",
});
expect(screen.getByText("All Providers")).toBeInTheDocument();
expect(screen.getByText("Production AWS")).toBeInTheDocument();
});
it("supports contextual placeholder and empty-selection copy", () => {
render(
<AccountsSelector
providers={providers}
placeholder="Select a Provider"
emptySelectionLabel="No provider selected"
clearSelectionLabel="Clear provider selection"
/>,
);
expect(screen.getByText("Select a Provider")).toBeInTheDocument();
expect(screen.getByText("No provider selected")).toBeInTheDocument();
});
it("allows disabling search explicitly", () => {
render(<AccountsSelector providers={providers} search={false} />);
expect(multiSelectContentSpy).toHaveBeenLastCalledWith(false);
});
it("passes visible account labels as search keywords instead of only the internal id", () => {
render(<AccountsSelector providers={providers} />);
expect(
screen.getByText("Production AWS").closest("[data-value]"),
).toHaveAttribute(
"data-keywords",
expect.stringContaining("Production AWS"),
);
expect(
screen.getByText("Production AWS").closest("[data-value]"),
).toHaveAttribute("data-keywords", expect.stringContaining("123456789012"));
});
it("can use provider UID values for pages whose API filters by provider_uid__in", () => {
render(
<AccountsSelector
providers={providers}
filterKey={FILTER_FIELD.PROVIDER_UID}
/>,
);
expect(
screen.getByText("Production AWS").closest("[data-value]"),
).toHaveAttribute("data-value", "123456789012");
});
it("disables select all when every account is already shown", () => {
render(<AccountsSelector providers={providers} />);
expect(
screen.getByRole("option", { name: /select all Providers/i }),
).toHaveAttribute("aria-disabled", "true");
expect(screen.getByText("All selected")).toBeInTheDocument();
});
it("marks configured account values as disabled", () => {
render(
<AccountsSelector
providers={providers}
disabledValues={["provider-1"]}
/>,
);
expect(
screen.getByText("Production AWS").closest("[data-value]"),
).toHaveAttribute("data-disabled", "true");
expect(screen.getByText("Disconnected")).toBeInTheDocument();
});
it("can close the dropdown after selecting a launch-scan provider", async () => {
const user = userEvent.setup();
render(
<AccountsSelector
providers={providers}
closeOnSelect
onBatchChange={vi.fn()}
selectedValues={[]}
/>,
);
await user.click(screen.getByRole("button", { name: /open selector/i }));
expect(multiSelectSpy).toHaveBeenLastCalledWith({ open: true });
await user.click(screen.getByRole("button", { name: /production aws/i }));
expect(multiSelectSpy).toHaveBeenLastCalledWith({ open: false });
});
it("shows the provider icon next to the name in the trigger for a single selection", async () => {
render(
<AccountsSelector
providers={providers}
onBatchChange={vi.fn()}
selectedValues={["provider-1"]}
/>,
);
const trigger = screen.getByTestId("trigger");
expect(await within(trigger).findByText("AWS")).toBeInTheDocument();
expect(within(trigger).getByText("Production AWS")).toBeInTheDocument();
});
it("renders one icon per selected account without deduping by provider type", async () => {
const secondAws = {
...providers[0],
id: "provider-2",
attributes: {
...providers[0].attributes,
uid: "999999999999",
alias: "Staging AWS",
},
};
render(
<AccountsSelector
providers={[providers[0], secondAws]}
onBatchChange={vi.fn()}
selectedValues={["provider-1", "provider-2"]}
/>,
);
const trigger = screen.getByTestId("trigger");
// Two AWS accounts -> two AWS icons in the trigger (no dedupe).
expect(await within(trigger).findAllByText("AWS")).toHaveLength(2);
expect(
within(trigger).getByText("2 Providers selected"),
).toBeInTheDocument();
});
});