mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Delete resource modal dark mode
This commit is contained in:
parent
267993b388
commit
b8e2ac46d1
2 changed files with 180 additions and 41 deletions
|
|
@ -1,68 +1,193 @@
|
|||
import { describe, it, expect, vi } from "vitest";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import { renderWithProviders, screen } from "../../../tests/test-utils";
|
||||
import DeleteResourceModal from "./DeleteResourceModal";
|
||||
|
||||
describe("DeleteResourceModal", () => {
|
||||
const mockOnCancel = vi.fn();
|
||||
const mockOnOk = vi.fn();
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
title: "Delete Resource",
|
||||
message: "Are you sure you want to delete this resource?",
|
||||
onCancel: vi.fn(),
|
||||
onOk: vi.fn(),
|
||||
onCancel: mockOnCancel,
|
||||
onOk: mockOnOk,
|
||||
confirmLoading: false,
|
||||
};
|
||||
|
||||
it("renders", () => {
|
||||
const { getByText } = renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
expect(getByText("Delete Resource")).toBeInTheDocument();
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders the title correctly", () => {
|
||||
const { getByText } = renderWithProviders(<DeleteResourceModal {...defaultProps} title="Custom Delete Title" />);
|
||||
expect(getByText("Custom Delete Title")).toBeInTheDocument();
|
||||
it("should render", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
expect(screen.getByText("Delete Resource")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the message correctly", () => {
|
||||
const { getByText } = renderWithProviders(
|
||||
<DeleteResourceModal {...defaultProps} message="This is a custom message" />,
|
||||
);
|
||||
expect(getByText("This is a custom message")).toBeInTheDocument();
|
||||
it("should render the title correctly", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} title="Custom Delete Title" />);
|
||||
expect(screen.getByText("Custom Delete Title")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the resourceInformation and resourceInformationTitle correctly", () => {
|
||||
it("should render the message correctly", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} message="This is a custom message" />);
|
||||
expect(screen.getByText("This is a custom message")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render alert message when provided", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} alertMessage="Warning: This action cannot be undone" />);
|
||||
expect(screen.getByText("Warning: This action cannot be undone")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render alert message when not provided", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render resourceInformation and resourceInformationTitle correctly", () => {
|
||||
const resourceInformation = [
|
||||
{ label: "Name", value: "Test Resource" },
|
||||
{ label: "ID", value: "123" },
|
||||
];
|
||||
const { getByText } = renderWithProviders(
|
||||
renderWithProviders(
|
||||
<DeleteResourceModal
|
||||
{...defaultProps}
|
||||
resourceInformationTitle="Resource Details"
|
||||
resourceInformation={resourceInformation}
|
||||
/>,
|
||||
);
|
||||
expect(getByText("Resource Details")).toBeInTheDocument();
|
||||
expect(getByText("Name")).toBeInTheDocument();
|
||||
expect(getByText("Test Resource")).toBeInTheDocument();
|
||||
expect(getByText("ID")).toBeInTheDocument();
|
||||
expect(getByText("123")).toBeInTheDocument();
|
||||
expect(screen.getByText("Resource Details")).toBeInTheDocument();
|
||||
expect(screen.getByText("Name")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Resource")).toBeInTheDocument();
|
||||
expect(screen.getByText("ID")).toBeInTheDocument();
|
||||
expect(screen.getByText("123")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables the delete button when requiredConfirmation is not in the input (empty state)", async () => {
|
||||
const { getByRole } = renderWithProviders(<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />);
|
||||
const deleteButton = getByRole("button", { name: /delete/i });
|
||||
it("should render dash for null or undefined resource information values", () => {
|
||||
const resourceInformation = [
|
||||
{ label: "Name", value: null },
|
||||
{ label: "ID", value: undefined },
|
||||
{ label: "Status", value: "Active" },
|
||||
];
|
||||
renderWithProviders(
|
||||
<DeleteResourceModal {...defaultProps} resourceInformation={resourceInformation} />,
|
||||
);
|
||||
expect(screen.getAllByText("-")).toHaveLength(2);
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render resource information with number values", () => {
|
||||
const resourceInformation = [{ label: "Count", value: 42 }];
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} resourceInformation={resourceInformation} />);
|
||||
expect(screen.getByText("42")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onCancel when cancel button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
const cancelButton = screen.getByRole("button", { name: "Cancel" });
|
||||
await user.click(cancelButton);
|
||||
expect(mockOnCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call onOk when delete button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
await user.click(deleteButton);
|
||||
expect(mockOnOk).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should disable delete button when requiredConfirmation is not entered", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />);
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables the delete button when the input equals requiredConfirmation", async () => {
|
||||
it("should disable delete button when requiredConfirmation input does not match exactly", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { getByRole, getByPlaceholderText } = renderWithProviders(
|
||||
<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />,
|
||||
);
|
||||
const input = getByPlaceholderText("DELETE");
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />);
|
||||
const input = screen.getByPlaceholderText("DELETE");
|
||||
await user.type(input, "DELET");
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should enable delete button when requiredConfirmation input matches exactly", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />);
|
||||
const input = screen.getByPlaceholderText("DELETE");
|
||||
await user.type(input, "DELETE");
|
||||
const deleteButton = getByRole("button", { name: /delete/i });
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
expect(deleteButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("should reset requiredConfirmation input when modal opens", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { rerender } = renderWithProviders(
|
||||
<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />,
|
||||
);
|
||||
const input = screen.getByPlaceholderText("DELETE");
|
||||
await user.type(input, "DELETE");
|
||||
expect(input).toHaveValue("DELETE");
|
||||
|
||||
rerender(<DeleteResourceModal {...defaultProps} isOpen={false} requiredConfirmation="DELETE" />);
|
||||
rerender(<DeleteResourceModal {...defaultProps} isOpen={true} requiredConfirmation="DELETE" />);
|
||||
|
||||
const newInput = screen.getByPlaceholderText("DELETE");
|
||||
expect(newInput).toHaveValue("");
|
||||
});
|
||||
|
||||
it("should display deleting text on delete button when confirmLoading is true", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} confirmLoading={true} />);
|
||||
expect(screen.getByText("Deleting...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display delete text on delete button when confirmLoading is false", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} confirmLoading={false} />);
|
||||
const deleteButton = screen.getByRole("button", { name: /delete/i });
|
||||
expect(deleteButton).toBeInTheDocument();
|
||||
expect(screen.queryByText("Deleting...")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should disable delete button when confirmLoading is true", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} confirmLoading={true} />);
|
||||
const deleteButton = screen.getByText("Deleting...").closest("button");
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should disable cancel button when confirmLoading is true", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} confirmLoading={true} />);
|
||||
const cancelButton = screen.getByRole("button", { name: "Cancel" });
|
||||
expect(cancelButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should disable delete button when confirmLoading is true even if requiredConfirmation matches", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(
|
||||
<DeleteResourceModal {...defaultProps} confirmLoading={true} requiredConfirmation="DELETE" />,
|
||||
);
|
||||
const input = screen.getByPlaceholderText("DELETE");
|
||||
await user.type(input, "DELETE");
|
||||
const deleteButton = screen.getByText("Deleting...").closest("button");
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should render required confirmation prompt with correct text", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} requiredConfirmation="DELETE" />);
|
||||
expect(screen.getByText(/Type/i)).toBeInTheDocument();
|
||||
expect(screen.getByText("DELETE")).toBeInTheDocument();
|
||||
expect(screen.getByText(/to confirm deletion/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render required confirmation section when not provided", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} />);
|
||||
expect(screen.queryByPlaceholderText("DELETE")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render modal when isOpen is false", () => {
|
||||
renderWithProviders(<DeleteResourceModal {...defaultProps} isOpen={false} />);
|
||||
expect(screen.queryByText("Delete Resource")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Alert, Descriptions, Input, Modal, Typography } from "antd";
|
||||
import { Alert, Card, Descriptions, Input, Modal, Typography, theme } from "antd";
|
||||
import { ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
interface DeleteResourceModalProps {
|
||||
|
|
@ -32,6 +33,7 @@ export default function DeleteResourceModal({
|
|||
requiredConfirmation,
|
||||
}: DeleteResourceModalProps) {
|
||||
const { Title, Text } = Typography;
|
||||
const { token } = theme.useToken();
|
||||
const [requiredConfirmationInput, setRequiredConfirmationInput] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -57,25 +59,36 @@ export default function DeleteResourceModal({
|
|||
>
|
||||
<div className="space-y-4">
|
||||
{alertMessage && <Alert message={alertMessage} type="warning" />}
|
||||
<div className="mt-4 p-4 bg-red-50 rounded-lg border border-red-200">
|
||||
<Title level={5} className="mb-3 text-gray-900">
|
||||
{resourceInformationTitle}
|
||||
</Title>
|
||||
<Card
|
||||
title={resourceInformationTitle}
|
||||
className="mt-4"
|
||||
styles={{
|
||||
body: { padding: "16px" },
|
||||
header: {
|
||||
backgroundColor: token.colorErrorBg,
|
||||
borderColor: token.colorErrorBorder,
|
||||
},
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: token.colorErrorBg,
|
||||
borderColor: token.colorErrorBorder,
|
||||
}}
|
||||
>
|
||||
<Descriptions column={1} size="small">
|
||||
{resourceInformation &&
|
||||
resourceInformation.map(({ label, value, ...textProps }) => (
|
||||
<Descriptions.Item key={label} label={<span className="font-semibold text-gray-700">{label}</span>}>
|
||||
<Descriptions.Item key={label} label={<span className="font-semibold">{label}</span>}>
|
||||
<Text {...textProps}>{value ?? "-"}</Text>
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
</div>
|
||||
</Card>
|
||||
<div>
|
||||
<Text>{message}</Text>
|
||||
</div>
|
||||
{requiredConfirmation && (
|
||||
<div className="mb-6 mt-4 pt-4 border-t border-gray-200">
|
||||
<Text className="block text-base font-medium text-gray-700 mb-2">
|
||||
<div className="mb-6 mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<Text className="block text-base font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<Text>Type </Text>
|
||||
<Text strong type="danger">
|
||||
{requiredConfirmation}
|
||||
|
|
@ -86,7 +99,8 @@ export default function DeleteResourceModal({
|
|||
value={requiredConfirmationInput}
|
||||
onChange={(e) => setRequiredConfirmationInput(e.target.value)}
|
||||
placeholder={requiredConfirmation}
|
||||
className="rounded-md text-base border-gray-200"
|
||||
className="rounded-md"
|
||||
prefix={<ExclamationCircleOutlined style={{ color: token.colorError }} />}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue