mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 09:51:36 +00:00
test(modal): add unit tests (#2145)
This commit is contained in:
parent
da2a308d31
commit
d4ca8b5c97
7 changed files with 341 additions and 93 deletions
|
@ -1,25 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Button, Modal } from "carbon-components-svelte";
|
||||
|
||||
let open = false;
|
||||
</script>
|
||||
|
||||
<Button on:click={() => (open = true)}>Create database</Button>
|
||||
|
||||
<Modal
|
||||
bind:open
|
||||
modalHeading="Create database"
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtons={[{ text: "Cancel" }, { text: "Duplicate" }]}
|
||||
secondaryButtonText="Cancel"
|
||||
on:click:button--secondary={({ detail }) => {
|
||||
console.log(detail);
|
||||
open = false;
|
||||
}}
|
||||
on:open
|
||||
on:close
|
||||
on:submit
|
||||
on:click:button--primary
|
||||
>
|
||||
<p>Create a new Cloudant database in the US South region.</p>
|
||||
</Modal>
|
57
tests/Modal/Modal.test.svelte
Normal file
57
tests/Modal/Modal.test.svelte
Normal file
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from "carbon-components-svelte";
|
||||
import type { ComponentProps } from "svelte";
|
||||
|
||||
export let open = false;
|
||||
export let modalHeading = "";
|
||||
export let modalLabel = "";
|
||||
export let modalAriaLabel = "";
|
||||
export let iconDescription = "Close the modal";
|
||||
export let hasForm = false;
|
||||
export let hasScrollingContent = false;
|
||||
export let primaryButtonText = "";
|
||||
export let primaryButtonDisabled = false;
|
||||
export let primaryButtonIcon = undefined;
|
||||
export let shouldSubmitOnEnter = true;
|
||||
export let secondaryButtonText = "";
|
||||
export let secondaryButtons: ComponentProps<Modal>["secondaryButtons"] =
|
||||
undefined;
|
||||
export let selectorPrimaryFocus = "[data-modal-primary-focus]";
|
||||
export let preventCloseOnClickOutside = false;
|
||||
export let size: ComponentProps<Modal>["size"] = undefined;
|
||||
export let danger = false;
|
||||
export let alert = false;
|
||||
export let passiveModal = false;
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
{open}
|
||||
{modalHeading}
|
||||
{modalLabel}
|
||||
{modalAriaLabel}
|
||||
{iconDescription}
|
||||
{hasForm}
|
||||
{hasScrollingContent}
|
||||
{primaryButtonText}
|
||||
{primaryButtonDisabled}
|
||||
{primaryButtonIcon}
|
||||
{shouldSubmitOnEnter}
|
||||
{secondaryButtonText}
|
||||
{secondaryButtons}
|
||||
{selectorPrimaryFocus}
|
||||
{preventCloseOnClickOutside}
|
||||
{size}
|
||||
{danger}
|
||||
{alert}
|
||||
{passiveModal}
|
||||
on:open={() => console.log("open")}
|
||||
on:close={() => console.log("close")}
|
||||
on:submit={() => console.log("submit")}
|
||||
on:click:button--primary={() => console.log("click:button--primary")}
|
||||
on:click:button--secondary={(e) =>
|
||||
console.log("click:button--secondary", e.detail)}
|
||||
on:transitionend={(e) => console.log("transitionend", e.detail)}
|
||||
>
|
||||
<slot />
|
||||
<input id="test-focus" data-testid="test-focus" />
|
||||
</Modal>
|
284
tests/Modal/Modal.test.ts
Normal file
284
tests/Modal/Modal.test.ts
Normal file
|
@ -0,0 +1,284 @@
|
|||
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");
|
||||
});
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from "carbon-components-svelte";
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
size="xs"
|
||||
open
|
||||
modalHeading="Create database"
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtonText="Cancel"
|
||||
on:click:button--secondary
|
||||
on:open
|
||||
on:close
|
||||
on:submit
|
||||
>
|
||||
<p>Create a new Cloudant database in the US South region.</p>
|
||||
</Modal>
|
|
@ -1,17 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from "carbon-components-svelte";
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
size="lg"
|
||||
open
|
||||
modalHeading="Create database"
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtonText="Cancel"
|
||||
on:click:button--secondary
|
||||
on:open
|
||||
on:close
|
||||
on:submit
|
||||
>
|
||||
<p>Create a new Cloudant database in the US South region.</p>
|
||||
</Modal>
|
|
@ -1,17 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from "carbon-components-svelte";
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
preventCloseOnClickOutside
|
||||
open
|
||||
modalHeading="Create database"
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtonText="Cancel"
|
||||
on:click:button--secondary
|
||||
on:open
|
||||
on:close
|
||||
on:submit
|
||||
>
|
||||
<p>Create a new Cloudant database in the US South region.</p>
|
||||
</Modal>
|
|
@ -1,17 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Modal } from "carbon-components-svelte";
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
size="sm"
|
||||
open
|
||||
modalHeading="Create database"
|
||||
primaryButtonText="Confirm"
|
||||
secondaryButtonText="Cancel"
|
||||
on:click:button--secondary
|
||||
on:open
|
||||
on:close
|
||||
on:submit
|
||||
>
|
||||
<p>Create a new Cloudant database in the US South region.</p>
|
||||
</Modal>
|
Loading…
Add table
Add a link
Reference in a new issue