@vertisanpro/flowbite-react
Version:
Non-Official React components built for Flowbite and Tailwind CSS
118 lines (117 loc) • 6.74 kB
JavaScript
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React, { createRef, useState } from 'react';
import { describe, expect, it } from 'vitest';
import { Button } from '../Button';
import { TextInput } from '../TextInput';
import { Modal } from './Modal';
describe('Components / Modal', () => {
it('should be closed by clicking outside if the "dismissible" prop is passed.', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, { dismissible: true }));
await user.click(triggerButton());
const modal = dialog();
expect(modal).toBeInTheDocument();
await user.click(dialogOverlay());
expect(modal).not.toBeInTheDocument();
});
it('should append to root element when root prop is provided', async () => {
const root = document.createElement('div');
const user = userEvent.setup();
render(React.createElement(TestModal, { root: root }));
const openButton = triggerButton();
await user.click(openButton);
expect(within(root).getByRole('dialog')).toBeTruthy();
});
describe('A11y', () => {
it('should have `role="dialog"`', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, null));
const openButton = triggerButton();
await user.click(openButton);
expect(dialog()).toBeDefined();
});
it('should have `aria-labelledby` equals to modal header id', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, null));
const openButton = triggerButton();
await user.click(openButton);
expect(dialog()).toHaveAttribute('aria-labelledby', 'test-dialog-header');
});
});
describe('Keyboard interactions', () => {
it('should open `Modal` when `Space` is pressed on its toggle button', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, null));
const openButton = triggerButton();
await user.click(openButton);
const modal = dialog();
expect(modal).toBeInTheDocument();
});
it('should close `Modal` when `Space` is pressed on any of its buttons', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, null));
const openButton = triggerButton();
await user.click(openButton);
const modal = dialog();
const closeButton = within(modal).getAllByRole('button')[0];
expect(modal).toBeInTheDocument();
await user.click(closeButton);
expect(modal).not.toBeInTheDocument();
});
it('should be closed by Esc key press.', async () => {
const user = userEvent.setup();
render(React.createElement(TestModal, { dismissible: true }));
await user.click(triggerButton());
const modal = dialog();
expect(modal).toBeInTheDocument();
await user.keyboard('[Escape]');
expect(modal).not.toBeInTheDocument();
});
it('should initially focus element provided by ref when `initialFocus={elementRef}`', async () => {
const user = userEvent.setup();
const inputRef = createRef();
render(React.createElement(TestModal, { dismissible: true, inputRef: inputRef }));
await user.click(triggerButton());
const modal = dialog();
expect(modal).toBeInTheDocument();
await waitFor(() => expect(document.activeElement).toEqual(input()));
});
it('should focus back to button toggle when closing Modal', async () => {
const user = userEvent.setup();
render(React.createElement("div", null,
React.createElement(TestModal, { dismissible: true }),
React.createElement("button", null, "Second button")));
await user.click(triggerButton());
const modal = dialog();
expect(modal).toBeInTheDocument();
await waitFor(() => expect(document.activeElement).toEqual(closeButton()));
await user.click(dialogOverlay());
expect(modal).not.toBeInTheDocument();
// The following element is only focusable in the testing environment
expect(document.activeElement).toEqual(document.body);
await user.tab();
expect(document.activeElement).toEqual(triggerButton());
});
});
});
const TestModal = ({ root, dismissible = false, inputRef, }) => {
const [open, setOpen] = useState(false);
return (React.createElement(React.Fragment, null,
React.createElement(Button, { onClick: () => setOpen(true) }, "Toggle modal"),
React.createElement(Modal, { dismissible: dismissible, root: root, show: open, onClose: () => setOpen(false), initialFocus: inputRef },
React.createElement(Modal.Header, { id: "test-dialog-header" }, "Terms of Service"),
React.createElement(Modal.Body, null,
React.createElement("div", { className: "space-y-6" },
React.createElement("p", { className: "text-base leading-relaxed text-gray-500 dark:text-gray-400" }, "With less than a month to go before the European Union enacts new consumer privacy laws for its citizens, companies around the world are updating their terms of service agreements to comply."),
React.createElement("p", { className: "text-base leading-relaxed text-gray-500 dark:text-gray-400" }, "The European Union\u2019s General Data Protection Regulation (G.D.P.R.) goes into effect on May 25 and is meant to ensure a common set of data rights in the European Union. It requires organizations to notify users as soon as possible of high-risk data breaches that could personally affect them.")),
React.createElement(TextInput, { ref: inputRef, "data-testid": "text-input" })),
React.createElement(Modal.Footer, null,
React.createElement(Button, { onClick: () => setOpen(false) }, "I accept"),
React.createElement(Button, { color: "gray", onClick: () => setOpen(false) }, "Decline")))));
};
const dialog = () => screen.getByRole('dialog');
const dialogOverlay = () => screen.getByTestId('modal-overlay');
const triggerButton = () => screen.getByRole('button', { name: 'Toggle modal' });
const input = () => screen.getByTestId('text-input');
const closeButton = () => screen.getByLabelText('Close');