mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-14 18:01:06 +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