UNPKG

@vertisanpro/flowbite-react

Version:

Non-Official React components built for Flowbite and Tailwind CSS

223 lines (222 loc) 10.6 kB
import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HiOutlineArrowCircleDown } from '@vertisanpro/react-icons/hi'; import React from 'react'; import { describe, expect, it } from 'vitest'; import { Flowbite } from '../Flowbite'; import { Accordion } from './Accordion'; describe('Components / Accordion', () => { describe('A11y', () => { it('should use `aria-label` if provided', () => { render(React.createElement(TestAccordion, { "aria-label": "My accordion" })); expect(accordion()).toHaveAccessibleName('My accordion'); }); it('should use `aria-labelledby=""` in `Accordion.Content` if provided', () => { render(React.createElement(TestAccordion, null)); expect(content()[0]).toHaveAccessibleName('Title'); expect(content()[0]).toHaveAttribute('aria-labelledby', 'accordion-title'); }); it('should use `role="button"` in `Accordion.Title`', () => { render(React.createElement(TestAccordion, null)); titles().forEach((title) => { expect(title).toBeInTheDocument(); }); }); it('should use `id=""` if provided in `Accordion.Title`', () => { render(React.createElement(TestAccordion, { "aria-label": "My accordion" })); expect(accordion()).toHaveAccessibleName('My accordion'); }); it("shouldn't include `arrowIcon` in `Accordion.Title` label", () => { render(React.createElement(TestAccordion, null)); titles().forEach((title) => expect(title).toHaveAccessibleName('Title')); }); }); describe('Keyboard interactions', () => { it('should open focused panel, and close others when `Space` is pressed on an `Accordion.Panel`', async () => { const user = userEvent.setup(); render(React.createElement(TestAccordion, null)); // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of titles()) { await user.tab(); } await user.keyboard('[Space]'); expect(content()[0]).not.toBeVisible(); expect(content()[1]).toBeVisible(); }); it('it should open and close self when `Space is pressed on the same`Accordion.Panel`', async () => { const user = userEvent.setup(); render(React.createElement(TestAccordion, null)); // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of titles()) { await user.tab(); } await user.keyboard('[Space]'); await user.keyboard('[Space]'); expect(content()[0]).not.toBeVisible(); expect(content()[1]).not.toBeVisible(); }); it('should open focused panel without closing others on an `Accordion.Panel` with `alwaysOpen={true}`', async () => { const user = userEvent.setup(); render(React.createElement(TestAccordion, { alwaysOpen: true })); // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of titles()) { await user.tab(); } await user.keyboard('[Space]'); expect(titles()[0]).toBeVisible(); expect(titles()[1]).toBeVisible(); }); it('should be possible to `Tab` out', async () => { const user = userEvent.setup(); render(React.createElement(React.Fragment, null, React.createElement(TestAccordion, null), React.createElement("button", { role: "checkbox" }, "Outside button"))); const outsideButton = screen.getByText('Outside button'); await waitFor(async () => { await user.tab(); expect(outsideButton).toHaveFocus(); }); }); it('should give each `Accordion.Title` focus in order while pressing `Tab`', async () => { const user = userEvent.setup(); render(React.createElement(TestAccordion, null)); for (const title of titles()) { await user.tab(); expect(title).toHaveFocus(); } }); }); describe('Props', () => { it('should use any HTML heading element in `Accordion.Title as=".."`', () => { render(React.createElement(TestAccordion, null)); expect(headings()[0].tagName.toLocaleLowerCase()).toEqual('h3'); expect(headings()[1].tagName.toLocaleLowerCase()).toEqual('h2'); }); }); describe('Theme', () => { describe('`Accordion`', () => { it('should use custom `base` classes', () => { const theme = { accordion: { root: { base: 'text-4xl', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestAccordion, null))); expect(accordion()).toHaveClass('text-4xl'); }); it('should use custom `flush` classes', () => { const theme = { accordion: { root: { flush: { off: 'text-4xl', on: 'text-3xl', }, }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestAccordion, null), React.createElement(TestAccordion, { flush: true }))); const accordions = screen.getAllByTestId('flowbite-accordion'); const normal = accordions[0]; const flush = accordions[1]; expect(normal).toHaveClass('text-4xl'); expect(flush).toHaveClass('text-3xl'); }); }); describe('`Accordion.Content`', () => { it('should use custom `content` classes', () => { const theme = { accordion: { content: { base: 'text-4xl', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestAccordion, null))); content().forEach((content) => { expect(content).toHaveClass('text-4xl'); }); }); }); describe('`Accordion.Title`', () => { it('should use custom `title` classes', () => { const theme = { accordion: { title: { arrow: { base: 'w-8 h-8', open: { off: '', on: 'text-purple-600', }, }, base: 'p-3', flush: { off: 'text-4xl', on: 'text-3xl', }, open: { off: 'text-gray-400', on: 'text-gray-600', }, }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestAccordion, { alwaysOpen: true }), React.createElement(TestAccordion, { alwaysOpen: true, flush: true }))); const normalTitles = [titles()[0], titles()[1]]; const flushTitles = [titles()[2], titles()[3]]; const openTitles = [titles()[0], titles()[2]]; const closedTitles = [titles()[1], titles()[3]]; titles().forEach((title) => { expect(title).toHaveClass('p-3'); }); normalTitles.forEach((title) => { expect(title).toHaveClass('text-4xl'); }); flushTitles.forEach((title) => { expect(title).toHaveClass('text-3xl'); }); openTitles.forEach((title) => { // Note: it is being overwrited by the className prop which is expected expect(title).toHaveClass('text-cyan-300'); }); closedTitles.forEach((title) => { expect(title).toHaveClass('text-gray-400'); }); }); }); }); describe('Click to toggle open', () => { beforeEach(() => { render(React.createElement(TestAccordion, null)); }); it('should open and close the accordion when title is clicked', async () => { const titleElements = titles(); await userEvent.click(titleElements[1]); // open second panel await userEvent.click(titleElements[1]); // close second panel expect(content()[0]).not.toBeVisible(); // content should not be visible expect(content()[1]).not.toBeVisible(); // content should not be visible }); }); }); const TestAccordion = (props) => (React.createElement(Accordion, { arrowIcon: HiOutlineArrowCircleDown, ...props }, React.createElement(Accordion.Panel, null, React.createElement(Accordion.Title, { as: "h3", className: "text-cyan-300", id: "accordion-title" }, "Title"), React.createElement(Accordion.Content, { "aria-labelledby": "accordion-title", className: "text-cyan-300" }, React.createElement("p", null, "Content"))), React.createElement(Accordion.Panel, null, React.createElement(Accordion.Title, null, "Title"), React.createElement(Accordion.Content, null, React.createElement("p", null, "Content"))))); const accordion = () => screen.getByTestId('flowbite-accordion'); const content = () => screen.getAllByTestId('flowbite-accordion-content'); const headings = () => screen.getAllByTestId('flowbite-accordion-heading'); const titles = () => screen.getAllByRole('button');