UNPKG

@vertisanpro/flowbite-react

Version:

Non-Official React components built for Flowbite and Tailwind CSS

276 lines (275 loc) 13.3 kB
import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { HiChartPie, HiInbox, HiShoppingBag } from '@vertisanpro/react-icons/hi'; import React from 'react'; import { describe, expect, it } from 'vitest'; import { Flowbite } from '../Flowbite'; import { Sidebar } from './Sidebar'; describe('Components / Sidebar', () => { describe('A11y', () => { it('should use `aria-label` if provided', () => { render(React.createElement(TestSidebar, { "aria-label": "My differently labelled sidebar" })); const sidebar = screen.getByLabelText('My differently labelled sidebar'); expect(sidebar).toHaveAccessibleName('My differently labelled sidebar'); }); it('should use text content as accessible name in `Sidebar.Collapse` and `Sidebar.Item`', async () => { const user = userEvent.setup(); const itemLabels = ['Dashboard', 'E-commerce', 'Products', 'Services', 'Inbox', 'My heading']; render(React.createElement(TestSidebar, null)); for (const collapse of collapseButtons()) { await user.click(collapse); } items().forEach((item, i) => { expect(item.firstElementChild).toHaveAccessibleName(itemLabels[i]); }); }); it('should use text content as accessible name in `Sidebar.Logo`', () => { render(React.createElement(TestSidebar, null)); expect(logo()).toHaveAccessibleName('Flowbite'); }); it('should use `imgAlt` as alternative text for image in `Sidebar.Logo` given `img=".." and imgAlt=".."`', () => { render(React.createElement(TestSidebar, null)); const logoImg = screen.getByAltText('Flowbite logo'); expect(logoImg).toHaveAccessibleName('Flowbite logo'); }); }); }); describe('Keyboard interactions', () => { it('should expand/collapse when `Space` is pressed on a `Sidebar.Collapse`', async () => { const user = userEvent.setup(); render(React.createElement(TestSidebar, null)); const collapseButton = collapseButtons()[0]; await user.click(collapseButton); const collapse = collapses()[0]; expect(collapse).toBeVisible(); }); it('should follow link when `Space` is pressed on `Sidebar.Item` with `href=".."`', () => { render(React.createElement(TestSidebar, null)); const link = screen.getAllByRole('link')[1]; expect(link).toHaveAccessibleName('Dashboard'); expect(link).toHaveAttribute('href', '#'); }); it('should be possible to `Tab` out', async () => { const user = userEvent.setup(); render(React.createElement(React.Fragment, null, React.createElement(TestSidebar, null), React.createElement("button", { role: "checkbox" }, "Outside"))); const outside = screen.getByText('Outside'); await waitFor(async () => { await user.tab(); expect(outside).toHaveFocus(); }); }); }); describe('Props', () => { it('shouldn\'t display anything when `collapseBehavior="hide"`', () => { render(React.createElement(TestSidebar, { collapseBehavior: "hide", collapsed: true })); const sidebar = screen.queryByLabelText('Sidebar'); expect(sidebar).not.toBeVisible(); }); it("shouldn't display `Sidebar.CTA` when `collapsed={true}`", () => { render(React.createElement(TestSidebar, { collapsed: true })); expect(cta()).not.toBeVisible(); }); it("shouldn't display text content in `Sidebar.Logo` when `collapsed={true}`", () => { render(React.createElement(TestSidebar, { collapsed: true })); expect(logo().lastElementChild).toHaveClass('hidden'); }); it('should use the HTML element provided in `Sidebar.Item as=".."`', () => { render(React.createElement(TestSidebar, null)); const asItem = screen.getByLabelText('My heading'); expect(asItem.tagName.toLocaleLowerCase()).toEqual('h3'); }); }); describe('Theme', () => { it('should use custom classes', () => { const theme = { sidebar: { root: { base: 'bg-gray-100', collapsed: { off: 'text-gray-200', on: 'text-gray-300', }, inner: 'bg-gray-200', }, }, }; const { getByLabelText } = render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, { "aria-label": "not-collapsed" }), React.createElement(TestSidebar, { "aria-label": "collapsed", collapsed: true }))); const sidebar = getByLabelText('not-collapsed'); const inner = sidebar.firstElementChild; const collapsedSidebar = getByLabelText('collapsed'); expect(sidebar).toHaveClass('bg-gray-100'); expect(sidebar).toHaveClass('text-gray-200'); expect(inner).toHaveClass('bg-gray-200'); expect(collapsedSidebar).toHaveClass('text-gray-300'); }); describe('`Sidebar.Collapse`', () => { it('should use custom classes', async () => { const user = userEvent.setup(); const theme = { sidebar: { collapse: { button: 'text-gray-100', icon: { base: 'text-gray-200', open: { off: 'bg-gray-100', on: 'bg-gray-200', }, }, label: { base: 'text-gray-300', icon: { base: 'text-gray-400', open: { on: '', off: '', }, }, }, list: 'bg-gray-300', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, null))); const labelIcons = collapseLabels().map((label) => label.nextElementSibling); collapseButtons().forEach((button) => expect(button).toHaveClass('text-gray-100')); collapseIcons().forEach((icon) => expect(icon).toHaveClass('text-gray-200 bg-gray-100')); collapseLabels().forEach((label) => expect(label).toHaveClass('text-gray-300')); labelIcons.forEach((labelicon) => expect(labelicon).toHaveClass('text-gray-400')); for (const button of collapseButtons()) { await user.click(button); } collapseIcons().forEach((icon) => expect(icon).toHaveClass('bg-gray-200')); }); }); describe('`Sidebar.CTA`', () => { it('should use custom classes', () => { const theme = { sidebar: { cta: { base: 'bg-gray-100', color: { primary: 'text-gray-100', }, }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, null))); expect(cta()).toHaveClass('bg-gray-100 text-gray-100'); }); }); describe('`Sidebar.Item`', () => { it('should use custom classes', () => { const theme = { sidebar: { item: { active: 'text-gray-100', base: 'bg-gray-100', collapsed: { insideCollapse: 'text-gray-300', }, content: { base: 'bg-gray-200', }, icon: { base: 'text-gray-400', active: 'bg-gray-300', }, }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, { collapsed: true }))); const theItems = items() .map((item) => item.firstElementChild) .map((item) => item?.firstElementChild) .filter((item) => item?.tagName.toLocaleLowerCase() !== 'button'); const activeItems = screen.getAllByTestId('active-item'); const activeIcons = activeItems.map((item) => item.firstElementChild); const inactiveIcons = [...collapseIcons().filter((icon) => !activeIcons.includes(icon))]; const inactiveItems = [...theItems.filter((item) => item !== null && !activeItems.includes(item))]; activeIcons.forEach((icon) => expect(icon).toHaveClass('bg-gray-300')); activeItems.forEach((item) => expect(item).toHaveClass('text-gray-100')); itemContents().forEach((content) => expect(content).toHaveClass('bg-gray-200')); inactiveIcons.forEach((icon) => expect(icon).not.toHaveClass('bg-gray-300')); inactiveItems.forEach((item) => expect(item).not.toHaveClass('text-gray-100')); icons().forEach((icon) => expect(icon).toHaveClass('text-gray-400')); theItems.forEach((item) => expect(item).toHaveClass('bg-gray-100')); }); }); describe('`Sidebar.Items`', () => { it('should use custom classes', () => { const theme = { sidebar: { items: { base: 'text-gray-100', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, null))); itemsContainers().forEach((container) => expect(container).toHaveClass('text-gray-100')); }); }); describe('`Sidebar.ItemGroup`', () => { it('should use custom classes', () => { const theme = { sidebar: { itemGroup: { base: 'text-gray-100', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, null))), itemGroups().forEach((group) => expect(group).toHaveClass('text-gray-100')); }); }); describe('`Sidebar.Logo`', () => { it('should use custom classes', () => { const theme = { sidebar: { logo: { base: 'text-gray-100', collapsed: { off: 'text-gray-300', on: 'text-gray-400', }, img: 'text-gray-200', }, }, }; render(React.createElement(Flowbite, { theme: { theme } }, React.createElement(TestSidebar, null))), expect(logo()).toHaveClass('text-gray-100'); }); }); }); const TestSidebar = ({ ...props }) => (React.createElement(Sidebar, { ...props }, React.createElement(Sidebar.Logo, { href: "#", img: "favicon.svg", imgAlt: "Flowbite logo" }, "Flowbite"), React.createElement(Sidebar.Items, null, React.createElement(Sidebar.ItemGroup, null, React.createElement(Sidebar.Item, { active: true, "data-testid": "active-item", href: "#", icon: HiChartPie, label: "3", labelColor: "success" }, "Dashboard"), React.createElement(Sidebar.Collapse, { "aria-label": "E-commerce", icon: HiShoppingBag }, React.createElement(Sidebar.Item, { href: "#" }, "Products"), React.createElement(Sidebar.Item, { href: "#" }, "Services")), React.createElement(Sidebar.Item, { href: "#", icon: HiInbox }, "Inbox"), React.createElement(Sidebar.Item, { as: "h3" }, "My heading"))), React.createElement(Sidebar.CTA, { color: "primary" }, "Some content"))); const collapseButtons = () => screen.getAllByRole('button'); const collapses = () => screen.getAllByRole('list').slice(1); const collapseIcons = () => screen.getAllByTestId('flowbite-sidebar-collapse-icon'); const collapseLabels = () => screen.getAllByTestId('flowbite-sidebar-collapse-label'); const cta = () => screen.getByText('Some content'); const itemContents = () => screen.getAllByTestId('flowbite-sidebar-item-content'); const itemGroups = () => screen.getAllByTestId('flowbite-sidebar-item-group'); const icons = () => screen.getAllByTestId('flowbite-sidebar-item-icon'); const items = () => screen.getAllByRole('listitem'); const itemsContainers = () => screen.getAllByTestId('flowbite-sidebar-items'); const logo = () => screen.getByLabelText('Flowbite');