mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
chore(ui): repair failing vitest tests after shadcn migration (batch A)
Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
4741e676fe
commit
3500823aa3
9 changed files with 286 additions and 310 deletions
|
|
@ -102,14 +102,11 @@ describe("AgentHubTableColumns", () => {
|
|||
|
||||
it("should display I/O modes", () => {
|
||||
render(<TestTable data={[mockAgent]} />);
|
||||
// "In:" and "Out:" are in <span> children; getByText with exact:false
|
||||
// matches against the element's full textContent across child nodes
|
||||
expect(screen.getByText((_, el) =>
|
||||
el?.tagName === "P" && el.textContent === "In: text"
|
||||
)).toBeInTheDocument();
|
||||
expect(screen.getByText((_, el) =>
|
||||
el?.tagName === "P" && el.textContent === "Out: text, image"
|
||||
)).toBeInTheDocument();
|
||||
// The label and value are sibling text nodes within the same container div.
|
||||
const inLabel = screen.getByText("In:");
|
||||
expect(inLabel.parentElement?.textContent).toContain("text");
|
||||
const outLabel = screen.getByText("Out:");
|
||||
expect(outLabel.parentElement?.textContent).toContain("text, image");
|
||||
});
|
||||
|
||||
it("should display 'Yes' badge for public agents", () => {
|
||||
|
|
|
|||
|
|
@ -2,41 +2,11 @@ import { render, screen, waitFor } from "@testing-library/react";
|
|||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
// Mock networking module
|
||||
const mockGetAgentsList = vi.fn();
|
||||
vi.mock("../networking", () => ({
|
||||
getAgentsList: (...args: any[]) => mockGetAgentsList(...args),
|
||||
}));
|
||||
|
||||
// Mock antd Select
|
||||
vi.mock("antd", () => {
|
||||
const SelectComponent = ({ children, onChange, value, mode, placeholder, loading, disabled, ...props }: any) => (
|
||||
<div data-testid="agent-select" data-loading={loading} data-disabled={disabled}>
|
||||
<select
|
||||
data-testid="select-input"
|
||||
multiple={mode === "multiple"}
|
||||
value={value || []}
|
||||
onChange={(e) => {
|
||||
const selected = Array.from(e.target.selectedOptions, (opt: any) => opt.value);
|
||||
onChange?.(selected);
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
{loading && <span data-testid="loading-indicator">Loading</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
SelectComponent.Option = ({ children, value, ...props }: any) => (
|
||||
<option value={value} {...props}>
|
||||
{children}
|
||||
</option>
|
||||
);
|
||||
|
||||
return { Select: SelectComponent };
|
||||
});
|
||||
|
||||
import AgentSelector from "./AgentSelector";
|
||||
|
||||
describe("AgentSelector", () => {
|
||||
|
|
@ -50,95 +20,125 @@ describe("AgentSelector", () => {
|
|||
mockGetAgentsList.mockResolvedValue({
|
||||
agents: [
|
||||
{ agent_id: "agent-1", agent_name: "Agent One" },
|
||||
{ agent_id: "agent-2", agent_name: "Agent Two", agent_access_groups: ["group-a", "group-b"] },
|
||||
{
|
||||
agent_id: "agent-2",
|
||||
agent_name: "Agent Two",
|
||||
agent_access_groups: ["group-a", "group-b"],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("renders the selector", () => {
|
||||
it("should render the selector trigger", () => {
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
expect(screen.getByTestId("agent-select")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /select agents/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("fetches agents on mount with access token", async () => {
|
||||
it("should fetch agents on mount with access token", async () => {
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetAgentsList).toHaveBeenCalledWith("test-token");
|
||||
});
|
||||
});
|
||||
|
||||
it("does not fetch when accessToken is empty", () => {
|
||||
it("should not fetch when accessToken is empty", () => {
|
||||
render(<AgentSelector {...defaultProps} accessToken="" />);
|
||||
expect(mockGetAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows loading state while fetching", async () => {
|
||||
// Keep the promise pending
|
||||
let resolve: any;
|
||||
mockGetAgentsList.mockReturnValue(new Promise((r) => { resolve = r; }));
|
||||
|
||||
it("should render agent options after fetch when popover is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
expect(screen.getByTestId("agent-select")).toHaveAttribute("data-loading", "true");
|
||||
|
||||
// Resolve to clean up
|
||||
resolve({ agents: [] });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("agent-select")).toHaveAttribute("data-loading", "false");
|
||||
expect(mockGetAgentsList).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders agent options after fetch", async () => {
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
await user.click(screen.getByRole("button", { name: /select agents/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Agent One")).toBeInTheDocument();
|
||||
expect(screen.getByText("Agent Two")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders access group options with group prefix", async () => {
|
||||
it("should render access group options with group prefix when popover is opened", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetAgentsList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /select agents/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("group-a")).toBeInTheDocument();
|
||||
expect(screen.getByText("group-b")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("respects disabled prop", () => {
|
||||
it("should respect disabled prop", () => {
|
||||
render(<AgentSelector {...defaultProps} disabled />);
|
||||
expect(screen.getByTestId("agent-select")).toHaveAttribute("data-disabled", "true");
|
||||
expect(screen.getByRole("button", { name: /select agents/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("handles API error gracefully", async () => {
|
||||
it("should handle API error gracefully", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
mockGetAgentsList.mockRejectedValue(new Error("API error"));
|
||||
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(consoleSpy).toHaveBeenCalledWith("Error fetching agents:", expect.any(Error));
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
"Error fetching agents:",
|
||||
expect.any(Error),
|
||||
);
|
||||
});
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("passes value as flattened selectedValues", async () => {
|
||||
it("should render selected agents and access groups as badges", async () => {
|
||||
render(
|
||||
<AgentSelector
|
||||
{...defaultProps}
|
||||
value={{ agents: ["agent-1"], accessGroups: ["group-a"] }}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByTestId("select-input");
|
||||
// The value should contain agent-1 and group:group-a
|
||||
expect(select).toBeInTheDocument();
|
||||
expect(mockGetAgentsList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Labels are resolved from the options list once the fetch completes
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Agent One")).toBeInTheDocument();
|
||||
expect(screen.getByText("group-a")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("handles null response from API", async () => {
|
||||
it("should handle null response from API without error", async () => {
|
||||
mockGetAgentsList.mockResolvedValue(null);
|
||||
render(<AgentSelector {...defaultProps} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("agent-select")).toHaveAttribute("data-loading", "false");
|
||||
expect(mockGetAgentsList).toHaveBeenCalled();
|
||||
});
|
||||
// Trigger still renders with placeholder
|
||||
expect(screen.getByRole("button", { name: /select agents/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onChange when an option is selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
render(<AgentSelector {...defaultProps} onChange={onChange} />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetAgentsList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /select agents/i }));
|
||||
await user.click(await screen.findByText("Agent One"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
agents: ["agent-1"],
|
||||
accessGroups: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ const AgentCard: React.FC<AgentCardProps> = ({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label="Delete agent"
|
||||
className="shrink-0 -mr-1 text-destructive hover:text-destructive hover:bg-destructive/10 h-7 w-7"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ describe("DurationSelect", () => {
|
|||
const dailyOption = screen.getByText("Daily");
|
||||
await user.click(dailyOption);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith("24h", expect.any(Object));
|
||||
expect(onChange).toHaveBeenCalledWith("24h");
|
||||
});
|
||||
|
||||
it("should accept and pass value prop to Select", () => {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,18 @@ describe("FiltersButton", () => {
|
|||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should show badge when hasActiveFilters is true", () => {
|
||||
it("should show active filters indicator when hasActiveFilters is true", () => {
|
||||
const onClick = vi.fn();
|
||||
const { container } = render(<FiltersButton onClick={onClick} active={false} hasActiveFilters={true} />);
|
||||
const button = screen.getByRole("button", { name: /filters/i });
|
||||
const badgeWrapper = button.closest(".ant-badge");
|
||||
expect(badgeWrapper).toBeInTheDocument();
|
||||
const indicator = container.querySelector("span.rounded-full");
|
||||
expect(indicator).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show active filters indicator when hasActiveFilters is false", () => {
|
||||
const onClick = vi.fn();
|
||||
const { container } = render(<FiltersButton onClick={onClick} active={false} hasActiveFilters={false} />);
|
||||
const indicator = container.querySelector("span.rounded-full");
|
||||
expect(indicator).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render custom label when provided", () => {
|
||||
|
|
|
|||
|
|
@ -3,62 +3,6 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
|
|||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import KeyLifecycleSettings from "./KeyLifecycleSettings";
|
||||
|
||||
vi.mock("antd", () => {
|
||||
const Option = ({ children, value }: any) => (
|
||||
<option value={value}>{children}</option>
|
||||
);
|
||||
const Select = ({ children, value, onChange, placeholder }: any) => (
|
||||
<select
|
||||
data-testid="select"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
data-placeholder={placeholder}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
);
|
||||
Select.Option = Option;
|
||||
return {
|
||||
Select,
|
||||
Tooltip: ({ children, title }: any) => (
|
||||
<div data-testid="tooltip" title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Switch: ({ checked, onChange }: any) => (
|
||||
<input
|
||||
type="checkbox"
|
||||
data-testid="switch"
|
||||
checked={checked}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
/>
|
||||
),
|
||||
Divider: () => <hr data-testid="divider" />,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@tremor/react", () => ({
|
||||
TextInput: ({ value, onValueChange, onChange, placeholder, name, className }: any) => {
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
if (onValueChange) {
|
||||
onValueChange(e.target.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<input
|
||||
data-testid={name === "duration" ? "duration-input" : "custom-interval-input"}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("KeyLifecycleSettings", () => {
|
||||
const mockForm = {
|
||||
getFieldValue: vi.fn(),
|
||||
|
|
@ -75,6 +19,17 @@ describe("KeyLifecycleSettings", () => {
|
|||
isCreateMode: false,
|
||||
};
|
||||
|
||||
const getDurationInput = () => {
|
||||
const inputs = screen.getAllByRole("textbox");
|
||||
const duration = inputs.find(
|
||||
(el) => (el as HTMLInputElement).name === "duration",
|
||||
);
|
||||
if (!duration) {
|
||||
throw new Error("duration input not found");
|
||||
}
|
||||
return duration as HTMLInputElement;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockForm.getFieldValue.mockReturnValue("");
|
||||
|
|
@ -92,68 +47,40 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Expire Key")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("duration-input")).toBeInTheDocument();
|
||||
expect(getDurationInput()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show correct placeholder in create mode", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} isCreateMode={true} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} isCreateMode={true} />,
|
||||
);
|
||||
|
||||
const input = screen.getByTestId("duration-input");
|
||||
expect(input).toHaveAttribute(
|
||||
expect(getDurationInput()).toHaveAttribute(
|
||||
"placeholder",
|
||||
"e.g., 30d or leave empty to never expire"
|
||||
"e.g., 30d or leave empty to never expire",
|
||||
);
|
||||
});
|
||||
|
||||
it("should show correct placeholder in edit mode", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} isCreateMode={false} />);
|
||||
|
||||
const input = screen.getByTestId("duration-input");
|
||||
expect(input).toHaveAttribute("placeholder", "e.g., 30d");
|
||||
});
|
||||
|
||||
it("should show correct tooltip in create mode", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} isCreateMode={true} />);
|
||||
|
||||
const tooltips = screen.getAllByTestId("tooltip");
|
||||
const expiryTooltip = tooltips.find((tooltip) =>
|
||||
tooltip.getAttribute("title")?.includes("Leave empty to keep the current expiry unchanged")
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} isCreateMode={false} />,
|
||||
);
|
||||
expect(expiryTooltip).toBeInTheDocument();
|
||||
expect(expiryTooltip).toHaveAttribute(
|
||||
"title",
|
||||
"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged."
|
||||
);
|
||||
});
|
||||
|
||||
it("should show correct tooltip in edit mode", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} isCreateMode={false} />);
|
||||
|
||||
const tooltips = screen.getAllByTestId("tooltip");
|
||||
const expiryTooltip = tooltips.find((tooltip) =>
|
||||
tooltip.getAttribute("title")?.includes("Leave empty to keep the current expiry unchanged")
|
||||
);
|
||||
expect(expiryTooltip).toBeInTheDocument();
|
||||
expect(expiryTooltip).toHaveAttribute(
|
||||
"title",
|
||||
"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to keep the current expiry unchanged."
|
||||
);
|
||||
expect(getDurationInput()).toHaveAttribute("placeholder", "e.g., 30d");
|
||||
});
|
||||
|
||||
it("should initialize with form value if present", () => {
|
||||
mockForm.getFieldValue.mockReturnValue("30d");
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} />);
|
||||
|
||||
const input = screen.getByTestId("duration-input") as HTMLInputElement;
|
||||
expect(input.value).toBe("30d");
|
||||
expect(getDurationInput().value).toBe("30d");
|
||||
});
|
||||
|
||||
it("should update form using setFieldValue when duration changes", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} />);
|
||||
|
||||
const input = screen.getByTestId("duration-input");
|
||||
await user.type(input, "60d");
|
||||
await user.type(getDurationInput(), "60d");
|
||||
|
||||
expect(mockForm.setFieldValue).toHaveBeenCalledWith("duration", "60d");
|
||||
});
|
||||
|
|
@ -165,13 +92,17 @@ describe("KeyLifecycleSettings", () => {
|
|||
setFieldsValue: vi.fn(),
|
||||
};
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} form={formWithoutSetFieldValue} />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
form={formWithoutSetFieldValue}
|
||||
/>,
|
||||
);
|
||||
|
||||
const input = screen.getByTestId("duration-input");
|
||||
await user.type(input, "90d");
|
||||
await user.type(getDurationInput(), "90d");
|
||||
|
||||
expect(formWithoutSetFieldValue.setFieldsValue).toHaveBeenCalledWith({ duration: "90d" });
|
||||
expect(formWithoutSetFieldValue.setFieldsValue).toHaveBeenCalledWith({
|
||||
duration: "90d",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -180,72 +111,112 @@ describe("KeyLifecycleSettings", () => {
|
|||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Enable Auto-Rotation")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("switch")).toBeInTheDocument();
|
||||
expect(screen.getByRole("switch")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show switch as unchecked when autoRotationEnabled is false", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={false} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const switchElement = screen.getByTestId("switch") as HTMLInputElement;
|
||||
expect(switchElement.checked).toBe(false);
|
||||
const switchElement = screen.getByRole("switch");
|
||||
expect(switchElement).toHaveAttribute("data-state", "unchecked");
|
||||
});
|
||||
|
||||
it("should show switch as checked when autoRotationEnabled is true", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} />,
|
||||
);
|
||||
|
||||
const switchElement = screen.getByTestId("switch") as HTMLInputElement;
|
||||
expect(switchElement.checked).toBe(true);
|
||||
const switchElement = screen.getByRole("switch");
|
||||
expect(switchElement).toHaveAttribute("data-state", "checked");
|
||||
});
|
||||
|
||||
it("should call onAutoRotationChange when switch is toggled", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onAutoRotationChange = vi.fn();
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} onAutoRotationChange={onAutoRotationChange} />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
onAutoRotationChange={onAutoRotationChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
const switchElement = screen.getByTestId("switch");
|
||||
await user.click(switchElement);
|
||||
await user.click(screen.getByRole("switch"));
|
||||
|
||||
expect(onAutoRotationChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("should not show rotation interval section when auto-rotation is disabled", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={false} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("Rotation Interval")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("select")).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show rotation interval section when auto-rotation is enabled", () => {
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="30d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="30d"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Rotation Interval")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("select")).toBeInTheDocument();
|
||||
expect(screen.getByRole("combobox")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show all predefined interval options", () => {
|
||||
it("should show all predefined interval options", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="30d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="30d"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("7 days")).toBeInTheDocument();
|
||||
expect(screen.getByText("30 days")).toBeInTheDocument();
|
||||
expect(screen.getByText("90 days")).toBeInTheDocument();
|
||||
expect(screen.getByText("180 days")).toBeInTheDocument();
|
||||
expect(screen.getByText("365 days")).toBeInTheDocument();
|
||||
expect(screen.getByText("Custom interval")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
|
||||
expect(
|
||||
screen.getByRole("option", { name: "7 days" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: "30 days" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: "90 days" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: "180 days" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: "365 days" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: "Custom interval" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display current rotation interval in select", () => {
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="90d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="90d"
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select") as HTMLSelectElement;
|
||||
expect(select.value).toBe("90d");
|
||||
expect(screen.getByRole("combobox")).toHaveTextContent("90 days");
|
||||
});
|
||||
|
||||
it("should call onRotationIntervalChange when predefined interval is selected", async () => {
|
||||
|
|
@ -257,11 +228,11 @@ describe("KeyLifecycleSettings", () => {
|
|||
autoRotationEnabled={true}
|
||||
rotationInterval="7d"
|
||||
onRotationIntervalChange={onRotationIntervalChange}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select");
|
||||
await user.selectOptions(select, "30d");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(screen.getByRole("option", { name: "30 days" }));
|
||||
|
||||
expect(onRotationIntervalChange).toHaveBeenCalledWith("30d");
|
||||
});
|
||||
|
|
@ -269,14 +240,26 @@ describe("KeyLifecycleSettings", () => {
|
|||
it("should show custom input when custom option is selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="30d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="30d"
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select");
|
||||
await user.selectOptions(select, "custom");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(
|
||||
screen.getByRole("option", { name: "Custom interval" }),
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("custom-interval-input")).toBeInTheDocument();
|
||||
expect(screen.getByText("Supported formats: seconds (s), minutes (m), hours (h), days (d)")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByPlaceholderText("e.g., 1s, 5m, 2h, 14d"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Supported formats: seconds (s), minutes (m), hours (h), days (d)",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should hide custom input when predefined interval is selected after custom", async () => {
|
||||
|
|
@ -288,13 +271,15 @@ describe("KeyLifecycleSettings", () => {
|
|||
autoRotationEnabled={true}
|
||||
rotationInterval="custom-value"
|
||||
onRotationIntervalChange={onRotationIntervalChange}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select");
|
||||
await user.selectOptions(select, "7d");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(screen.getByRole("option", { name: "7 days" }));
|
||||
|
||||
expect(screen.queryByTestId("custom-interval-input")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByPlaceholderText("e.g., 1s, 5m, 2h, 14d"),
|
||||
).not.toBeInTheDocument();
|
||||
expect(onRotationIntervalChange).toHaveBeenCalledWith("7d");
|
||||
});
|
||||
|
||||
|
|
@ -307,55 +292,75 @@ describe("KeyLifecycleSettings", () => {
|
|||
autoRotationEnabled={true}
|
||||
rotationInterval=""
|
||||
onRotationIntervalChange={onRotationIntervalChange}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select");
|
||||
await user.selectOptions(select, "custom");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(
|
||||
screen.getByRole("option", { name: "Custom interval" }),
|
||||
);
|
||||
|
||||
const customInput = screen.getByTestId("custom-interval-input");
|
||||
const customInput = screen.getByPlaceholderText("e.g., 1s, 5m, 2h, 14d");
|
||||
await user.type(customInput, "14d");
|
||||
|
||||
expect(onRotationIntervalChange).toHaveBeenCalledWith("14d");
|
||||
});
|
||||
|
||||
it("should show info message when auto-rotation is enabled", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period."
|
||||
)
|
||||
"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show info message when auto-rotation is disabled", () => {
|
||||
renderWithProviders(<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={false} />);
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText(
|
||||
"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period."
|
||||
)
|
||||
"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period.",
|
||||
),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should initialize with custom interval input visible when custom interval is provided", () => {
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="14d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="14d"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("custom-interval-input")).toBeInTheDocument();
|
||||
const customInput = screen.getByTestId("custom-interval-input") as HTMLInputElement;
|
||||
const customInput = screen.getByPlaceholderText(
|
||||
"e.g., 1s, 5m, 2h, 14d",
|
||||
) as HTMLInputElement;
|
||||
expect(customInput).toBeInTheDocument();
|
||||
expect(customInput.value).toBe("14d");
|
||||
});
|
||||
|
||||
it("should show custom option selected when custom interval is provided", () => {
|
||||
renderWithProviders(
|
||||
<KeyLifecycleSettings {...defaultProps} autoRotationEnabled={true} rotationInterval="14d" />
|
||||
<KeyLifecycleSettings
|
||||
{...defaultProps}
|
||||
autoRotationEnabled={true}
|
||||
rotationInterval="14d"
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select") as HTMLSelectElement;
|
||||
expect(select.value).toBe("custom");
|
||||
expect(screen.getByRole("combobox")).toHaveTextContent(
|
||||
"Custom interval",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not call onRotationIntervalChange when selecting custom option", async () => {
|
||||
|
|
@ -367,11 +372,13 @@ describe("KeyLifecycleSettings", () => {
|
|||
autoRotationEnabled={true}
|
||||
rotationInterval="30d"
|
||||
onRotationIntervalChange={onRotationIntervalChange}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
const select = screen.getByTestId("select");
|
||||
await user.selectOptions(select, "custom");
|
||||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(
|
||||
screen.getByRole("option", { name: "Custom interval" }),
|
||||
);
|
||||
|
||||
expect(onRotationIntervalChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,22 +36,25 @@ describe("LabeledField", () => {
|
|||
});
|
||||
|
||||
it("should not be copyable when value is empty", () => {
|
||||
const { container } = render(<LabeledField label="User ID" value="" copyable />);
|
||||
// antd adds a .ant-typography-copy element when copyable; should not be present
|
||||
expect(container.querySelector(".ant-typography-copy")).not.toBeInTheDocument();
|
||||
render(<LabeledField label="User ID" value="" copyable />);
|
||||
expect(
|
||||
screen.queryByRole("button", { name: /copy user id/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not be copyable when value is default_user_id and defaultUserIdCheck is true", () => {
|
||||
const { container } = render(
|
||||
render(
|
||||
<LabeledField label="User ID" value="default_user_id" copyable defaultUserIdCheck />,
|
||||
);
|
||||
expect(container.querySelector(".ant-typography-copy")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: /copy user id/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should be copyable when copyable is true and value is present", () => {
|
||||
const { container } = render(
|
||||
<LabeledField label="User ID" value="user-123" copyable />,
|
||||
);
|
||||
expect(container.querySelector(".ant-typography-copy")).toBeInTheDocument();
|
||||
render(<LabeledField label="User ID" value="user-123" copyable />);
|
||||
expect(
|
||||
screen.getByRole("button", { name: /copy user id/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@ describe("OrganizationDropdown", () => {
|
|||
await user.click(screen.getByRole("combobox"));
|
||||
await user.click(await screen.findByText("Engineering"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith("org-1", expect.anything());
|
||||
expect(onChange).toHaveBeenCalledWith("org-1");
|
||||
});
|
||||
|
||||
it("should add ant-select-disabled class when disabled prop is true", () => {
|
||||
const { container } = render(<OrganizationDropdown organizations={MOCK_ORGS} disabled={true} />);
|
||||
expect(container.querySelector(".ant-select-disabled")).toBeTruthy();
|
||||
it("should be disabled when disabled prop is true", () => {
|
||||
render(<OrganizationDropdown organizations={MOCK_ORGS} disabled={true} />);
|
||||
expect(screen.getByRole("combobox")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should render with empty organizations list", () => {
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ describe("TableHeaderSortDropdown", () => {
|
|||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState={false} onSortChange={onSortChange} />);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
await user.click(screen.getByRole("button"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Ascending")).toBeInTheDocument();
|
||||
expect(screen.getByText("Descending")).toBeInTheDocument();
|
||||
expect(screen.getByText("Reset")).toBeInTheDocument();
|
||||
expect(screen.getByRole("menuitem", { name: /ascending/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("menuitem", { name: /descending/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("menuitem", { name: /reset/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -30,15 +29,8 @@ describe("TableHeaderSortDropdown", () => {
|
|||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState={false} onSortChange={onSortChange} />);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Ascending")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const ascendingOption = screen.getByText("Ascending");
|
||||
await user.click(ascendingOption);
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(await screen.findByRole("menuitem", { name: /ascending/i }));
|
||||
|
||||
expect(onSortChange).toHaveBeenCalledTimes(1);
|
||||
expect(onSortChange).toHaveBeenCalledWith("asc");
|
||||
|
|
@ -49,15 +41,8 @@ describe("TableHeaderSortDropdown", () => {
|
|||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState={false} onSortChange={onSortChange} />);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Descending")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const descendingOption = screen.getByText("Descending");
|
||||
await user.click(descendingOption);
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(await screen.findByRole("menuitem", { name: /descending/i }));
|
||||
|
||||
expect(onSortChange).toHaveBeenCalledTimes(1);
|
||||
expect(onSortChange).toHaveBeenCalledWith("desc");
|
||||
|
|
@ -68,65 +53,43 @@ describe("TableHeaderSortDropdown", () => {
|
|||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState="asc" onSortChange={onSortChange} />);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Reset")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const resetOption = screen.getByText("Reset");
|
||||
await user.click(resetOption);
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(await screen.findByRole("menuitem", { name: /reset/i }));
|
||||
|
||||
expect(onSortChange).toHaveBeenCalledTimes(1);
|
||||
expect(onSortChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("should highlight ascending option when sort state is asc", async () => {
|
||||
const user = userEvent.setup();
|
||||
it("should visually indicate ascending sort on the trigger when sort state is asc", () => {
|
||||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState="asc" onSortChange={onSortChange} />);
|
||||
|
||||
const { container } = render(
|
||||
<TableHeaderSortDropdown sortState="asc" onSortChange={onSortChange} />,
|
||||
);
|
||||
// The trigger is colored text-primary when sorted, not text-muted-foreground
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
const ascendingOption = screen.getByText("Ascending");
|
||||
const menuItem = ascendingOption.closest(".ant-dropdown-menu-item");
|
||||
expect(menuItem).toHaveClass("ant-dropdown-menu-item-selected");
|
||||
});
|
||||
expect(button.className).toMatch(/text-primary/);
|
||||
// And the chevron-up icon is rendered
|
||||
expect(container.querySelector("svg.lucide-chevron-up")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should highlight descending option when sort state is desc", async () => {
|
||||
const user = userEvent.setup();
|
||||
it("should visually indicate descending sort on the trigger when sort state is desc", () => {
|
||||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState="desc" onSortChange={onSortChange} />);
|
||||
|
||||
const { container } = render(
|
||||
<TableHeaderSortDropdown sortState="desc" onSortChange={onSortChange} />,
|
||||
);
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
const descendingOption = screen.getByText("Descending");
|
||||
const menuItem = descendingOption.closest(".ant-dropdown-menu-item");
|
||||
expect(menuItem).toHaveClass("ant-dropdown-menu-item-selected");
|
||||
});
|
||||
expect(button.className).toMatch(/text-primary/);
|
||||
expect(container.querySelector("svg.lucide-chevron-down")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not highlight any option when sort state is false", async () => {
|
||||
const user = userEvent.setup();
|
||||
it("should not visually indicate sort on the trigger when sort state is false", () => {
|
||||
const onSortChange = vi.fn();
|
||||
render(<TableHeaderSortDropdown sortState={false} onSortChange={onSortChange} />);
|
||||
|
||||
const { container } = render(
|
||||
<TableHeaderSortDropdown sortState={false} onSortChange={onSortChange} />,
|
||||
);
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Ascending")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const ascendingOption = screen.getByText("Ascending");
|
||||
const menuItem = ascendingOption.closest(".ant-dropdown-menu-item");
|
||||
expect(menuItem).not.toHaveClass("ant-dropdown-menu-item-selected");
|
||||
expect(button.className).toMatch(/text-muted-foreground/);
|
||||
expect(container.querySelector("svg.lucide-arrow-up-down")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should stop event propagation when button is clicked", async () => {
|
||||
|
|
@ -140,8 +103,7 @@ describe("TableHeaderSortDropdown", () => {
|
|||
</div>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
await user.click(button);
|
||||
await user.click(screen.getByRole("button"));
|
||||
|
||||
expect(onParentClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue