carbon-components-svelte/tests/Modal/Modal.test.ts
2025-04-12 12:55:39 -07:00

284 lines
7.9 KiB
TypeScript

import { render, screen } from "@testing-library/svelte";
import { tick } from "svelte";
import { user } from "../setup-tests";
import ModalTest from "./Modal.test.svelte";
describe("Modal", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders with default props", async () => {
const { container } = render(ModalTest, {
props: {
open: true,
modalHeading: "Test Modal",
primaryButtonText: "Save",
secondaryButtonText: "Cancel",
},
});
// Check if modal container is rendered
const modalContainer = container.querySelector(".bx--modal-container");
expect(modalContainer).toBeInTheDocument();
// Check if modal heading is rendered
expect(screen.getByText("Test Modal")).toBeInTheDocument();
// Check if buttons are rendered
expect(screen.getByText("Save")).toBeInTheDocument();
expect(screen.getByText("Cancel")).toBeInTheDocument();
// Check if close button is rendered
const closeButton = screen.getByLabelText("Close the modal");
expect(closeButton).toBeInTheDocument();
// Check if modal has correct ARIA attributes
expect(modalContainer).toHaveAttribute("role", "dialog");
expect(modalContainer).toHaveAttribute("aria-modal", "true");
expect(modalContainer).toHaveAttribute("aria-label", "Test Modal");
});
it("renders with basic structure", () => {
render(ModalTest, {
props: {
open: true,
modalHeading: "Test Modal",
primaryButtonText: "Save",
secondaryButtonText: "Cancel",
},
});
expect(screen.getByRole("dialog")).toBeInTheDocument();
expect(screen.getByText("Test Modal")).toBeInTheDocument();
expect(screen.getByText("Save")).toBeInTheDocument();
expect(screen.getByText("Cancel")).toBeInTheDocument();
expect(screen.getByLabelText("Close the modal")).toBeInTheDocument();
expect(screen.getByRole("dialog")).toHaveAttribute("aria-modal", "true");
});
it("opens and closes properly", async () => {
const consoleLog = vi.spyOn(console, "log");
const { component } = render(ModalTest, {
props: {
open: false,
modalHeading: "Test Modal",
},
});
// Open the modal
component.$set({ open: true });
await tick();
expect(screen.getByRole("dialog")).toBeInTheDocument();
expect(consoleLog).toHaveBeenCalledWith("open");
// Close the modal
component.$set({ open: false });
await tick();
expect(consoleLog).toHaveBeenCalledWith("close");
});
it("handles form submission", async () => {
const consoleLog = vi.spyOn(console, "log");
render(ModalTest, {
props: {
open: true,
hasForm: true,
modalHeading: "Form Modal",
primaryButtonText: "Save",
},
});
const primaryButton = screen.getByRole("button", { name: "Save" });
await user.click(primaryButton);
expect(consoleLog).toHaveBeenCalledWith("submit");
expect(consoleLog).toHaveBeenCalledWith("click:button--primary");
});
it("handles button clicks", async () => {
const consoleLog = vi.spyOn(console, "log");
render(ModalTest, {
props: {
open: true,
primaryButtonText: "Save",
secondaryButtonText: "Cancel",
},
});
await user.click(screen.getByText("Save"));
expect(consoleLog).toHaveBeenCalledWith("click:button--primary");
await user.click(screen.getByText("Cancel"));
expect(consoleLog).toHaveBeenCalledWith("click:button--secondary", {
text: "Cancel",
});
});
it("supports different modal sizes", () => {
type Size = "xs" | "sm" | "lg";
const sizeMappings = {
xs: "bx--modal-container--xs",
sm: "bx--modal-container--sm",
lg: "bx--modal-container--lg",
} as const;
// Test specific sizes
(Object.keys(sizeMappings) as Size[]).forEach((size) => {
const { unmount } = render(ModalTest, {
props: {
open: true,
size,
modalHeading: `${size} Modal`,
},
});
const modal = screen.getByRole("dialog");
expect(modal).toHaveClass(sizeMappings[size]);
unmount();
});
// Test default (medium) size
const { unmount } = render(ModalTest, {
props: {
open: true,
modalHeading: "Medium Modal",
},
});
const modal = screen.getByRole("dialog");
expect(modal).toHaveClass("bx--modal-container");
expect(modal).not.toHaveClass("bx--modal-container--xs");
expect(modal).not.toHaveClass("bx--modal-container--sm");
expect(modal).not.toHaveClass("bx--modal-container--lg");
unmount();
});
it("supports danger and alert variants", () => {
render(ModalTest, {
props: {
open: true,
danger: true,
alert: true,
modalHeading: "Danger Alert Modal",
primaryButtonText: "Delete",
},
});
const primaryButton = screen.getByRole("button", { name: "Delete" });
expect(primaryButton).toHaveClass("bx--btn--danger");
const modal = screen.getByRole("alertdialog");
expect(modal).toHaveAttribute("aria-label", "Danger Alert Modal");
});
it("handles scrolling content", () => {
render(ModalTest, {
props: {
open: true,
hasScrollingContent: true,
modalHeading: "Scrolling Modal",
},
});
const modalBody = screen.getByRole("region");
expect(modalBody).toHaveClass("bx--modal-scroll-content");
});
it("should focus close button when open", async () => {
render(ModalTest, {
props: {
open: true,
},
});
const closeButton = screen.getByLabelText("Close the modal");
expect(closeButton).toHaveFocus();
});
it("respects the selectorPrimaryFocus prop", async () => {
render(ModalTest, {
props: {
open: true,
modalHeading: "Focus Test",
selectorPrimaryFocus: "#test-focus",
},
});
expect(screen.getByTestId("test-focus")).toHaveFocus();
});
it("prevents closing when clicking outside if configured", async () => {
const { component } = render(ModalTest, {
props: {
open: true,
preventCloseOnClickOutside: true,
modalHeading: "Prevent Close Test",
},
});
const closeHandler = vi.fn();
component.$on("close", closeHandler);
// Click outside the modal
await user.click(document.body);
expect(closeHandler).not.toHaveBeenCalled();
});
it("supports passive modal variant", () => {
render(ModalTest, {
props: {
open: true,
passiveModal: true,
modalHeading: "Passive Modal",
primaryButtonText: "Save",
secondaryButtonText: "Cancel",
},
});
// Verify close button is in header
const closeButton = screen.getByLabelText("Close the modal");
expect(closeButton.closest(".bx--modal-header")).toBeInTheDocument();
// Verify no footer is present
expect(
screen.queryByRole("button", { name: "Save" }),
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Cancel" }),
).not.toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Close the modal" }),
).toBeInTheDocument();
});
it("handles closing through various methods", async () => {
const consoleLog = vi.spyOn(console, "log");
const { component } = render(ModalTest, {
props: {
open: true,
modalHeading: "Close Test Modal",
},
});
// Close via escape key
await user.keyboard("{Escape}");
expect(consoleLog).toHaveBeenCalledWith("close");
component.$set({ open: true });
await tick();
expect(consoleLog).toHaveBeenCalledWith("open");
// Close via clicking outside
await user.click(document.body);
expect(consoleLog).toHaveBeenCalledWith("close");
component.$set({ open: true });
await tick();
// Close via close button
const closeButton = screen.getByLabelText("Close the modal");
await user.click(closeButton);
expect(consoleLog).toHaveBeenCalledWith("close");
});
});