AlertDialog component
Implements AlertDialog with compound component pattern (Root, Content, Title, Description, Trigger, Cancel, Action, Portal, Overlay). Content uses role=alertdialog, aria-modal, aria-labelledby/describedby, focus trap, and scroll lock. Does not dismiss on Escape key. 8 tests passing.
This commit is contained in:
parent
30ee5c877e
commit
3388dbd371
@ -0,0 +1,25 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Action. */
|
||||||
|
export interface AlertDialogActionProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Confirms the action and closes the AlertDialog. */
|
||||||
|
export function AlertDialogAction(props: AlertDialogActionProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children", "onClick"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
|
||||||
|
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
|
||||||
|
if (typeof local.onClick === "function") local.onClick(e);
|
||||||
|
ctx.setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" onClick={handleClick} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Cancel. */
|
||||||
|
export interface AlertDialogCancelProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cancels the action and closes the AlertDialog. */
|
||||||
|
export function AlertDialogCancel(props: AlertDialogCancelProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children", "onClick"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
|
||||||
|
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
|
||||||
|
if (typeof local.onClick === "function") local.onClick(e);
|
||||||
|
ctx.setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" onClick={handleClick} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Show, createEffect, onCleanup, splitProps } from "solid-js";
|
||||||
|
import { createFocusTrap } from "../../utilities/focus-trap/create-focus-trap";
|
||||||
|
import { createScrollLock } from "../../utilities/scroll-lock/create-scroll-lock";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Content. */
|
||||||
|
export interface AlertDialogContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
|
forceMount?: boolean;
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertDialog content panel. Uses focus trap and scroll lock.
|
||||||
|
* Does NOT dismiss on Escape or outside pointer click (unlike Dialog).
|
||||||
|
*/
|
||||||
|
export function AlertDialogContent(props: AlertDialogContentProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children", "forceMount"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
let contentRef: HTMLDivElement | undefined;
|
||||||
|
|
||||||
|
const focusTrap = createFocusTrap(() => contentRef ?? null);
|
||||||
|
const scrollLock = createScrollLock();
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (ctx.isOpen()) {
|
||||||
|
focusTrap.activate();
|
||||||
|
scrollLock.lock();
|
||||||
|
} else {
|
||||||
|
focusTrap.deactivate();
|
||||||
|
scrollLock.unlock();
|
||||||
|
}
|
||||||
|
onCleanup(() => {
|
||||||
|
focusTrap.deactivate();
|
||||||
|
scrollLock.unlock();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={local.forceMount || ctx.isOpen()}>
|
||||||
|
<div
|
||||||
|
ref={contentRef}
|
||||||
|
id={ctx.contentId()}
|
||||||
|
role="alertdialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby={ctx.titleId() ?? undefined}
|
||||||
|
aria-describedby={ctx.descriptionId() ?? undefined}
|
||||||
|
data-state={ctx.isOpen() ? "open" : "closed"}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{local.children}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import type { Accessor } from "solid-js";
|
||||||
|
import { createContext, useContext } from "solid-js";
|
||||||
|
|
||||||
|
/** Internal context shared between all AlertDialog parts. */
|
||||||
|
export interface InternalAlertDialogContextValue {
|
||||||
|
isOpen: Accessor<boolean>;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
contentId: Accessor<string>;
|
||||||
|
titleId: Accessor<string | undefined>;
|
||||||
|
setTitleId: (id: string | undefined) => void;
|
||||||
|
descriptionId: Accessor<string | undefined>;
|
||||||
|
setDescriptionId: (id: string | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InternalAlertDialogContext = createContext<InternalAlertDialogContextValue>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the internal AlertDialog context. Throws if used outside <AlertDialog>.
|
||||||
|
*/
|
||||||
|
export function useInternalAlertDialogContext(): InternalAlertDialogContextValue {
|
||||||
|
const ctx = useContext(InternalAlertDialogContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error(
|
||||||
|
"[PettyUI] AlertDialog parts must be used inside <AlertDialog>.\n" +
|
||||||
|
" Fix: Wrap AlertDialog.Content, AlertDialog.Trigger, etc. inside <AlertDialog>.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InternalAlertDialogContextProvider = InternalAlertDialogContext.Provider;
|
||||||
|
|
||||||
|
/** Public context exposed to consumers via AlertDialog.useContext(). */
|
||||||
|
export interface AlertDialogContextValue {
|
||||||
|
/** Whether the alert dialog is currently open. */
|
||||||
|
open: Accessor<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlertDialogContext = createContext<AlertDialogContextValue>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public AlertDialog context. Throws if used outside <AlertDialog>.
|
||||||
|
*/
|
||||||
|
export function useAlertDialogContext(): AlertDialogContextValue {
|
||||||
|
const ctx = useContext(AlertDialogContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("[PettyUI] AlertDialog.useContext() called outside of <AlertDialog>.");
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertDialogContextProvider = AlertDialogContext.Provider;
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Description. */
|
||||||
|
export interface AlertDialogDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Description for the AlertDialog. Registers its ID for aria-describedby. */
|
||||||
|
export function AlertDialogDescription(props: AlertDialogDescriptionProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
const id = createUniqueId();
|
||||||
|
onMount(() => ctx.setDescriptionId(id));
|
||||||
|
onCleanup(() => ctx.setDescriptionId(undefined));
|
||||||
|
return (
|
||||||
|
<p id={id} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Show, splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Overlay. */
|
||||||
|
export interface AlertDialogOverlayProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||||
|
forceMount?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Backdrop overlay behind AlertDialog content. */
|
||||||
|
export function AlertDialogOverlay(props: AlertDialogOverlayProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["forceMount"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
return (
|
||||||
|
<Show when={local.forceMount || ctx.isOpen()}>
|
||||||
|
<div aria-hidden="true" data-state={ctx.isOpen() ? "open" : "closed"} {...rest} />
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Portal } from "../../utilities/portal/portal";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Portal. */
|
||||||
|
export interface AlertDialogPortalProps {
|
||||||
|
target?: Element | null;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Renders children into a portal (defaults to document.body). */
|
||||||
|
export function AlertDialogPortal(props: AlertDialogPortalProps): JSX.Element {
|
||||||
|
return props.target !== undefined ? (
|
||||||
|
<Portal target={props.target}>{props.children}</Portal>
|
||||||
|
) : (
|
||||||
|
<Portal>{props.children}</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createUniqueId } from "solid-js";
|
||||||
|
import {
|
||||||
|
type CreateDisclosureStateOptions,
|
||||||
|
createDisclosureState,
|
||||||
|
} from "../../primitives/create-disclosure-state";
|
||||||
|
import { createRegisterId } from "../../primitives/create-register-id";
|
||||||
|
import {
|
||||||
|
AlertDialogContextProvider,
|
||||||
|
InternalAlertDialogContextProvider,
|
||||||
|
type InternalAlertDialogContextValue,
|
||||||
|
} from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for the AlertDialog root. */
|
||||||
|
export interface AlertDialogRootProps {
|
||||||
|
open?: boolean;
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root component for AlertDialog. Manages open state and provides context.
|
||||||
|
*/
|
||||||
|
export function AlertDialogRoot(props: AlertDialogRootProps): JSX.Element {
|
||||||
|
const disclosure = createDisclosureState({
|
||||||
|
get open() {
|
||||||
|
return props.open;
|
||||||
|
},
|
||||||
|
get defaultOpen() {
|
||||||
|
return props.defaultOpen;
|
||||||
|
},
|
||||||
|
get onOpenChange() {
|
||||||
|
return props.onOpenChange;
|
||||||
|
},
|
||||||
|
} as CreateDisclosureStateOptions);
|
||||||
|
|
||||||
|
const contentId = createUniqueId();
|
||||||
|
const [titleId, setTitleId] = createRegisterId();
|
||||||
|
const [descriptionId, setDescriptionId] = createRegisterId();
|
||||||
|
|
||||||
|
const internalCtx: InternalAlertDialogContextValue = {
|
||||||
|
isOpen: disclosure.isOpen,
|
||||||
|
setOpen: (open) => (open ? disclosure.open() : disclosure.close()),
|
||||||
|
contentId: () => contentId,
|
||||||
|
titleId,
|
||||||
|
setTitleId,
|
||||||
|
descriptionId,
|
||||||
|
setDescriptionId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InternalAlertDialogContextProvider value={internalCtx}>
|
||||||
|
<AlertDialogContextProvider value={{ open: disclosure.isOpen }}>
|
||||||
|
{props.children}
|
||||||
|
</AlertDialogContextProvider>
|
||||||
|
</InternalAlertDialogContextProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { createUniqueId, onCleanup, onMount, splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Title. */
|
||||||
|
export interface AlertDialogTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Title for the AlertDialog. Registers its ID for aria-labelledby. */
|
||||||
|
export function AlertDialogTitle(props: AlertDialogTitleProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
const id = createUniqueId();
|
||||||
|
onMount(() => ctx.setTitleId(id));
|
||||||
|
onCleanup(() => ctx.setTitleId(undefined));
|
||||||
|
return (
|
||||||
|
<h2 id={id} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { splitProps } from "solid-js";
|
||||||
|
import { useInternalAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
|
||||||
|
/** Props for AlertDialog.Trigger. */
|
||||||
|
export interface AlertDialogTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Opens the AlertDialog when clicked. */
|
||||||
|
export function AlertDialogTrigger(props: AlertDialogTriggerProps): JSX.Element {
|
||||||
|
const [local, rest] = splitProps(props, ["children", "onClick"]);
|
||||||
|
const ctx = useInternalAlertDialogContext();
|
||||||
|
|
||||||
|
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
|
||||||
|
if (typeof local.onClick === "function") local.onClick(e);
|
||||||
|
ctx.setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button type="button" aria-haspopup="dialog" onClick={handleClick} {...rest}>
|
||||||
|
{local.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
packages/core/src/components/alert-dialog/index.ts
Normal file
33
packages/core/src/components/alert-dialog/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { AlertDialogAction } from "./alert-dialog-action";
|
||||||
|
import { AlertDialogCancel } from "./alert-dialog-cancel";
|
||||||
|
import { AlertDialogContent } from "./alert-dialog-content";
|
||||||
|
import { useAlertDialogContext } from "./alert-dialog-context";
|
||||||
|
import { AlertDialogDescription } from "./alert-dialog-description";
|
||||||
|
import { AlertDialogOverlay } from "./alert-dialog-overlay";
|
||||||
|
import { AlertDialogPortal } from "./alert-dialog-portal";
|
||||||
|
import { AlertDialogRoot } from "./alert-dialog-root";
|
||||||
|
import { AlertDialogTitle } from "./alert-dialog-title";
|
||||||
|
import { AlertDialogTrigger } from "./alert-dialog-trigger";
|
||||||
|
|
||||||
|
export const AlertDialog = Object.assign(AlertDialogRoot, {
|
||||||
|
Content: AlertDialogContent,
|
||||||
|
Title: AlertDialogTitle,
|
||||||
|
Description: AlertDialogDescription,
|
||||||
|
Trigger: AlertDialogTrigger,
|
||||||
|
Cancel: AlertDialogCancel,
|
||||||
|
Action: AlertDialogAction,
|
||||||
|
Portal: AlertDialogPortal,
|
||||||
|
Overlay: AlertDialogOverlay,
|
||||||
|
useContext: useAlertDialogContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type { AlertDialogRootProps } from "./alert-dialog-root";
|
||||||
|
export type { AlertDialogContentProps } from "./alert-dialog-content";
|
||||||
|
export type { AlertDialogTitleProps } from "./alert-dialog-title";
|
||||||
|
export type { AlertDialogDescriptionProps } from "./alert-dialog-description";
|
||||||
|
export type { AlertDialogTriggerProps } from "./alert-dialog-trigger";
|
||||||
|
export type { AlertDialogCancelProps } from "./alert-dialog-cancel";
|
||||||
|
export type { AlertDialogActionProps } from "./alert-dialog-action";
|
||||||
|
export type { AlertDialogPortalProps } from "./alert-dialog-portal";
|
||||||
|
export type { AlertDialogOverlayProps } from "./alert-dialog-overlay";
|
||||||
|
export type { AlertDialogContextValue } from "./alert-dialog-context";
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
// packages/core/tests/components/alert-dialog/alert-dialog.test.tsx
|
||||||
|
import { fireEvent, render, screen } from "@solidjs/testing-library";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { AlertDialog } from "../../../src/components/alert-dialog/index";
|
||||||
|
|
||||||
|
describe("AlertDialog", () => {
|
||||||
|
it("content has role=alertdialog", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>Are you sure?</AlertDialog.Description>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByRole("alertdialog")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("content has aria-modal=true", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
expect(screen.getByRole("alertdialog").getAttribute("aria-modal")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("content is linked to title via aria-labelledby", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Delete file?</AlertDialog.Title>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
const dialog = screen.getByRole("alertdialog");
|
||||||
|
const title = screen.getByText("Delete file?");
|
||||||
|
expect(dialog.getAttribute("aria-labelledby")).toBe(title.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("content is linked to description via aria-describedby", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
<AlertDialog.Description>This cannot be undone.</AlertDialog.Description>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
const dialog = screen.getByRole("alertdialog");
|
||||||
|
const desc = screen.getByText("This cannot be undone.");
|
||||||
|
expect(dialog.getAttribute("aria-describedby")).toBe(desc.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trigger opens the dialog", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialog.Trigger>Delete</AlertDialog.Trigger>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
expect(screen.queryByRole("alertdialog")).toBeNull();
|
||||||
|
fireEvent.click(screen.getByText("Delete"));
|
||||||
|
expect(screen.getByRole("alertdialog")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Cancel button closes the dialog", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
fireEvent.click(screen.getByText("Cancel"));
|
||||||
|
expect(screen.queryByRole("alertdialog")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Action button closes the dialog", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Are you sure?</AlertDialog.Title>
|
||||||
|
<AlertDialog.Action>Confirm</AlertDialog.Action>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
fireEvent.click(screen.getByText("Confirm"));
|
||||||
|
expect(screen.queryByRole("alertdialog")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Escape key does NOT close alert dialog", () => {
|
||||||
|
render(() => (
|
||||||
|
<AlertDialog defaultOpen>
|
||||||
|
<AlertDialog.Content>
|
||||||
|
<AlertDialog.Title>Confirm</AlertDialog.Title>
|
||||||
|
</AlertDialog.Content>
|
||||||
|
</AlertDialog>
|
||||||
|
));
|
||||||
|
fireEvent.keyDown(document, { key: "Escape" });
|
||||||
|
expect(screen.getByRole("alertdialog")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user