diff --git a/packages/ui/package.json b/packages/ui/package.json
index 05b4754f..0a63b966 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -47,6 +47,7 @@
"@rollup/plugin-typescript": "11.1.6",
"@testing-library/react": "15.0.7",
"@testing-library/jest-dom": "6.4.5",
+ "@testing-library/user-event": "14.5.2",
"@types/lodash.kebabcase": "4.1.9",
"@types/jest": "29.5.12",
"@types/react-dom": "18.2.19",
diff --git a/packages/ui/src/components/Markdown/Markdown.test.tsx b/packages/ui/src/components/Markdown/Markdown.test.tsx
index 29ecf879..5912c594 100644
--- a/packages/ui/src/components/Markdown/Markdown.test.tsx
+++ b/packages/ui/src/components/Markdown/Markdown.test.tsx
@@ -12,7 +12,7 @@ describe('Markdown', () => {
});
it('should render the Markdown component with richtext class', () => {
const { container } = render(Hello World!);
- const text = container.querySelector('.utrecht-rich-text');
+ const text = container.querySelector(':only-child');
expect(text).toBeInTheDocument();
});
it('should render custom component', () => {
diff --git a/packages/ui/src/components/Navigation/NavigationIcon/index.test.tsx b/packages/ui/src/components/Navigation/NavigationIcon/index.test.tsx
new file mode 100644
index 00000000..f255288a
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationIcon/index.test.tsx
@@ -0,0 +1,29 @@
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { createRef } from 'react';
+import { NavigationIcon } from './index';
+
+describe('NavigationIcon', () => {
+ it('renders the hamburger icon', () => {
+ const { container } = render();
+ expect(container.querySelector('svg')).toHaveAttribute('aria-hidden', 'true');
+ expect(container.querySelectorAll('line')).toHaveLength(3);
+ });
+
+ it('renders the close icon', () => {
+ const { container } = render();
+ expect(container.querySelector('svg')).toHaveAttribute('aria-hidden', 'true');
+ expect(container.querySelectorAll('line')).toHaveLength(2);
+ });
+
+ it('renders the icon with the provided ref', () => {
+ const ref = createRef();
+ const { container } = render();
+ expect(ref.current).toBe(container.querySelector('svg'));
+ });
+
+ it('renders a design system BEM class name', () => {
+ const { container } = render();
+ expect(container.querySelector('svg')).toHaveClass('utrecht-topnav__icon');
+ });
+});
diff --git a/packages/ui/src/components/Navigation/NavigationItem/index.test.tsx b/packages/ui/src/components/Navigation/NavigationItem/index.test.tsx
new file mode 100644
index 00000000..0e645e36
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationItem/index.test.tsx
@@ -0,0 +1,31 @@
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { createRef } from 'react';
+import { NavigationItem } from './index';
+
+describe('NavigationItem', () => {
+ it('renders NavigationItem component', () => {
+ const { container } = render(Test);
+ const navItem = container.querySelector(':only-child');
+ expect(navItem).toBeInTheDocument();
+ });
+
+ it('renders a design system BEM class name', () => {
+ const { container } = render(Test);
+ const navItem = container.querySelector(':only-child');
+ expect(navItem).toHaveClass('utrecht-navigation__item');
+ });
+
+ it('should render a list item with the appropriate class name when mobile', () => {
+ const { container } = render(Test);
+ const navItem = container.querySelector(':only-child');
+ expect(navItem).toHaveClass('utrecht-navigation__item--mobile');
+ expect(navItem).toHaveClass('utrecht-navigation__item-icon');
+ });
+
+ it('should forward the ref to the list item', () => {
+ const ref = createRef();
+ const { container } = render(Test);
+ expect(container.firstChild).toBe(ref.current);
+ });
+});
diff --git a/packages/ui/src/components/Navigation/NavigationLink/index.test.tsx b/packages/ui/src/components/Navigation/NavigationLink/index.test.tsx
new file mode 100644
index 00000000..4eb2047b
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationLink/index.test.tsx
@@ -0,0 +1,85 @@
+import { fireEvent, render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { createRef } from 'react';
+import { NavigationLink } from './index';
+
+describe('NavigationLink', () => {
+ it('renders the NavigationLink component', () => {
+ const { container } = render(Home);
+ const link = container.querySelector(':only-child');
+ expect(link).toBeInTheDocument();
+ });
+ it('applies the correct class when the link is current', () => {
+ const { container } = render(
+
+ Home
+ ,
+ );
+ const link = container.querySelector(':only-child');
+ expect(link).toHaveClass('utrecht-navigation__link--is-current');
+ });
+ it('calls the onClick handler when clicked', () => {
+ const onClick = jest.fn();
+ const { container } = render(
+
+ Home
+ ,
+ );
+ const link = container.querySelector(':only-child');
+ fireEvent.click(link as HTMLAnchorElement);
+ expect(onClick).toHaveBeenCalled();
+ });
+ it('renders the marker when provided', () => {
+ const { container } = render(
+ Marker}>
+ Home
+ ,
+ );
+ const marker = container.querySelector('span');
+ expect(marker).toBeInTheDocument();
+ });
+ it('renders the children when provided', () => {
+ const { container } = render(Home);
+ const link = container.querySelector(':only-child');
+ expect(link).toHaveTextContent('Home');
+ });
+ it('applies the correct class when the link is mobile', () => {
+ const { container } = render(
+
+ Home
+ ,
+ );
+ const link = container.querySelector(':only-child');
+ expect(link).toHaveClass('utrecht-navigation__link--mobile');
+ });
+ it('sets the correct href attribute', () => {
+ const { container } = render(Home);
+ const link = container.querySelector(':only-child');
+ expect(link).toHaveAttribute('href', '/');
+ });
+ it('sets the correct aria-current attribute', () => {
+ const { container } = render(
+
+ Home
+ ,
+ );
+ const link = container.querySelector(':only-child');
+ expect(link).toHaveAttribute('aria-current', 'page');
+ });
+ it('renders the correct role attribute', () => {
+ const { getByRole } = render(Home);
+ const link = getByRole('link');
+ expect(link).toBeInTheDocument();
+ });
+ it('supports ForwardRef in React', () => {
+ const ref = createRef();
+
+ const { container } = render(
+
+ Home
+ ,
+ );
+ const link = container.querySelector(':only-child');
+ expect(ref.current).toBe(link);
+ });
+});
diff --git a/packages/ui/src/components/Navigation/NavigationLink/index.tsx b/packages/ui/src/components/Navigation/NavigationLink/index.tsx
index 12cdefb1..1ae879e0 100644
--- a/packages/ui/src/components/Navigation/NavigationLink/index.tsx
+++ b/packages/ui/src/components/Navigation/NavigationLink/index.tsx
@@ -4,7 +4,6 @@ import { AnchorHTMLAttributes, ForwardedRef, forwardRef, PropsWithChildren, Reac
import styles from './index.module.scss';
interface NavigationLinkProps extends Omit, 'placeholder'> {
- placeholder?: boolean;
mobile?: boolean;
isCurrent?: boolean;
marker?: ReactNode;
diff --git a/packages/ui/src/components/Navigation/NavigationList/index.test.tsx b/packages/ui/src/components/Navigation/NavigationList/index.test.tsx
new file mode 100644
index 00000000..70bd3096
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationList/index.test.tsx
@@ -0,0 +1,118 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { NavigationList } from './index';
+import { NavigationListType } from '../index';
+
+const listData: NavigationListType[] = [
+ {
+ href: '#',
+ textContent: 'Home',
+ children: [
+ {
+ href: '#',
+ textContent: 'Sublink 1',
+ },
+ {
+ href: '#',
+ textContent: 'Sublink 2',
+ },
+ ],
+ },
+ {
+ href: '#',
+ textContent: 'About',
+ },
+];
+describe('NavigationList', () => {
+ it('renders NavigationList component', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ expect(navigationList).toBeInTheDocument();
+ });
+ it('renders a design system BEM class name', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ expect(navigationList).toHaveClass('utrecht-navigation__list');
+ });
+ it('renders NavigationList component with children', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ const navListItem = container.querySelectorAll('li a');
+ navListItem.forEach((item) => {
+ expect(item).toBeInTheDocument();
+ expect(item).toHaveAttribute('href');
+ expect(item).toHaveTextContent(/Home|About/);
+ });
+ expect(navListItem.length).toBe(2);
+ expect(navigationList).toBeInTheDocument();
+ });
+ describe('Mobile', () => {
+ it('renders NavigationList component with children and mobile prop', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ const navListItem = container.querySelectorAll('li');
+ navListItem.forEach((item) => {
+ expect(item).toBeInTheDocument();
+ expect(item).toHaveClass('utrecht-navigation__item--mobile');
+ });
+ expect(navigationList).toBeInTheDocument();
+ expect(navigationList).toHaveClass('utrecht-navigation__list--mobile');
+ });
+ it('renders NavigationList component with children and filled marker prop by default', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ const navListItem = navigationList?.querySelectorAll('li a')[0];
+ const marker = navListItem?.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ expect(marker).toHaveClass('utrecht-navigation__marker');
+ expect(marker).toHaveClass('utrecht-navigation__marker--fill');
+ expect(navListItem).toHaveClass('utrecht-navigation__link--mobile');
+ expect(navListItem).toBeInTheDocument();
+ expect(navigationList).toBeInTheDocument();
+ });
+ it('renders NavigationList component with children and outlined marker prop', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ const navListItem = navigationList?.querySelectorAll('li a')[1];
+ const marker = navListItem?.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ expect(marker).toHaveClass('utrecht-navigation__marker');
+ expect(marker).toHaveClass('utrecht-navigation__marker--outline');
+ expect(navListItem).toHaveClass('utrecht-navigation__link--mobile');
+ expect(navListItem).toBeInTheDocument();
+ expect(navigationList).toBeInTheDocument();
+ });
+ });
+
+ it('renders NavigationList component with children and sideNav prop', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ expect(navigationList).toBeInTheDocument();
+ expect(navigationList).toHaveClass('utrecht-navigation__list--side-nav');
+ });
+ it('renders NavigationList component with children and subList prop', () => {
+ const { container } = render();
+ const navigationList = container.querySelector(':only-child');
+ const navListItem = navigationList?.querySelectorAll('li');
+ navListItem?.forEach((item) => {
+ const subList = item.querySelectorAll('li ul');
+ subList?.forEach((subItem) => {
+ expect(subItem).toBeInTheDocument();
+ expect(subItem).toHaveClass('utrecht-navigation__list--sub-list');
+ });
+ expect(item).toBeInTheDocument();
+ expect(item).toHaveClass('utrecht-navigation__item');
+ });
+ expect(navigationList).toBeInTheDocument();
+ expect(navigationList).toHaveClass('utrecht-navigation__list--sub-list');
+ });
+ test('focuses on the first link when the list receives focus', () => {
+ render();
+ const firstLink = screen.getByText('Home');
+ const navList = firstLink.closest('ul');
+ expect(navList).toHaveAttribute('tabIndex', '-1');
+
+ fireEvent.focus(navList as HTMLUListElement);
+ expect(firstLink).toHaveFocus();
+ });
+});
diff --git a/packages/ui/src/components/Navigation/NavigationMarker/index.test.tsx b/packages/ui/src/components/Navigation/NavigationMarker/index.test.tsx
new file mode 100644
index 00000000..fe25a372
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationMarker/index.test.tsx
@@ -0,0 +1,49 @@
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+// import { createRef } from 'react';
+import { NavigationMarker } from './index';
+
+describe('NavigationMarker', () => {
+ it('renders a NavigationMarker', () => {
+ const { container } = render();
+ const marker = container.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ });
+ it('renders a design system BEM class name', () => {
+ const { container } = render();
+ const marker = container.querySelector(':only-child');
+ expect(marker).toHaveClass('utrecht-navigation__marker');
+ });
+ it('renders a fill marker by default', () => {
+ const { container } = render();
+ const marker = container.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ expect(marker).toHaveClass('utrecht-navigation__marker--fill');
+ expect(marker).not.toHaveClass('utrecht-navigation__marker--outline');
+ });
+
+ it('renders an outline marker when appearance is set to outline', () => {
+ const { container } = render();
+ const marker = container.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ expect(marker).toHaveClass('utrecht-navigation__marker--outline');
+ expect(marker).not.toHaveClass('utrecht-navigation__marker--fill');
+ });
+
+ it('renders a current marker when isCurrent is true', () => {
+ const { container } = render();
+ const marker = container.querySelector(':only-child');
+ expect(marker).toBeInTheDocument();
+ expect(marker).toHaveClass('utrecht-navigation__marker--current');
+ });
+ // TODO add ForwardRef support to the component
+ // it('supports ForwardRef in React', () => {
+ // const ref = createRef();
+
+ // const { container } = render();
+
+ // const drawer = container.querySelector(':only-child');
+
+ // expect(ref.current).toBe(drawer);
+ // });
+});
diff --git a/packages/ui/src/components/Navigation/NavigationToggleButton/index.test.tsx b/packages/ui/src/components/Navigation/NavigationToggleButton/index.test.tsx
new file mode 100644
index 00000000..d59a6682
--- /dev/null
+++ b/packages/ui/src/components/Navigation/NavigationToggleButton/index.test.tsx
@@ -0,0 +1,58 @@
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { createRef } from 'react';
+import React from 'react';
+import { NavToggleButton } from './index';
+
+describe('NavToggleButton', () => {
+ it('renders NavToggleButton component', () => {
+ const { container } = render();
+ const navToggleButton = container.querySelector(':only-child');
+ expect(navToggleButton).toBeInTheDocument();
+ });
+ it('renders a design system BEM class name', () => {
+ const { container } = render();
+ const navToggleButton = container.querySelector(':only-child');
+ expect(navToggleButton).toHaveClass('utrecht-navigation__toggle-button');
+ });
+ describe('aria attributes', () => {
+ it('renders with aria-haspopup="menu"', () => {
+ const { container } = render();
+ const navToggleButton = container.querySelector('button[aria-haspopup="menu"]');
+ expect(navToggleButton).toBeInTheDocument();
+ });
+ it('renders with aria-controls="menu"', () => {
+ const { container } = render();
+ const navToggleButton = container.querySelector('button[aria-haspopup="menu"]');
+ expect(navToggleButton).toBeInTheDocument();
+ });
+ });
+ it('renders a button with the hamburger icon and text', () => {
+ const { container } = render();
+ const button = container.querySelector(':only-child');
+ expect(button).toHaveTextContent('Open menu');
+ const svg = button?.querySelector('svg');
+ expect(svg?.querySelectorAll('line')).toHaveLength(3);
+ });
+ it('renders a button with the close icon and text', () => {
+ const { container } = render();
+ const button = container.querySelector(':only-child');
+ expect(button).toHaveTextContent('Close menu');
+ const svg = button?.querySelector('svg');
+ expect(svg?.querySelectorAll('line')).toHaveLength(2);
+ });
+ it('supports ForwardRef in React', () => {
+ const ref = createRef();
+ const { container } = render();
+ const button = container.querySelector('button');
+ expect(ref.current).toBe(button);
+ expect(button).toBeInTheDocument();
+ });
+ it('renders a button with the subtle-button appearance', () => {
+ const { container } = render();
+ const element = container.querySelector(':only-child');
+ const button = element?.querySelector(':only-child');
+ expect(button).toHaveClass('utrecht-button');
+ expect(button).toHaveClass('utrecht-button--subtle');
+ });
+});
diff --git a/packages/ui/src/components/Navigation/index.test.tsx b/packages/ui/src/components/Navigation/index.test.tsx
new file mode 100644
index 00000000..20b01d63
--- /dev/null
+++ b/packages/ui/src/components/Navigation/index.test.tsx
@@ -0,0 +1,204 @@
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+import { Navigation } from './index';
+import { useScreenSize } from '../../hooks';
+import { useClickOutside } from '../../hooks';
+import { useLockBody } from '../../hooks/useLockBody';
+jest.mock('../../hooks/useClickOutside.ts', () => ({
+ useClickOutside: jest.fn(),
+}));
+
+jest.mock('../../hooks/useLockBody', () => ({
+ useLockBody: jest.fn(),
+}));
+
+jest.mock('../../hooks/useScreenSize.ts');
+
+const mockList = [
+ { textContent: 'Home', href: '/' },
+ { textContent: 'About', href: '/about' },
+];
+const mockToggleButton = {
+ openText: 'Open Menu',
+ closeText: 'Close Menu',
+};
+describe('Navigation Component', () => {
+ beforeEach(() => {
+ (useScreenSize as jest.Mock).mockReturnValue(1024); // default desktop size
+ HTMLDialogElement.prototype.show = jest.fn(function () {
+ // eslint-disable-next-line no-invalid-this
+ this.open = true;
+ });
+
+ HTMLDialogElement.prototype.showModal = jest.fn(function () {
+ // eslint-disable-next-line no-invalid-this
+ this.open = true;
+ });
+
+ HTMLDialogElement.prototype.close = jest.fn(function () {
+ // eslint-disable-next-line no-invalid-this
+ this.open = false;
+ });
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+ it('renders Navigation component', () => {
+ const { container } = render();
+ const nav = container.querySelector(':only-child');
+ expect(nav).toBeInTheDocument();
+ });
+ test('should render the Navigation list on desktop view', () => {
+ const { container } = render();
+ const nav = container.querySelector('nav');
+ const navList = nav?.querySelector(':only-child');
+ const navListItems = navList?.querySelectorAll('li a');
+
+ expect(navListItems?.length).toBe(2);
+ navListItems?.forEach((item) => {
+ expect(item).toBeInTheDocument();
+ expect(item).toHaveAttribute('href');
+ expect(item).toHaveTextContent(/Home|About/);
+ });
+ expect(navList).toBeInTheDocument();
+ expect(nav).toBeInTheDocument();
+ });
+
+ test('should render the hamburger button in mobile view', () => {
+ (useScreenSize as jest.Mock).mockReturnValue(500); // mobile size
+ const { container } = render();
+ const nav = container.querySelector('nav');
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+
+ expect(hamburgerButton).toBeInTheDocument();
+ expect(nav).toBeInTheDocument();
+ });
+
+ test('should toggle the dialog when hamburger button is clicked', () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // mobile size
+ const { container } = render();
+ const nav = container.querySelector('nav');
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+
+ fireEvent.click(hamburgerButton as HTMLButtonElement);
+ const dialog = container.querySelector('dialog[open]');
+ expect(nav).toBeInTheDocument();
+ expect(dialog).toBeInTheDocument();
+ });
+
+ test('should close the dialog when close button is clicked', async () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // mobile size
+ const user = userEvent.setup();
+ const { container } = render();
+ const nav = container.querySelector('nav');
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+
+ await user.click(hamburgerButton as HTMLButtonElement);
+
+ const dialog = container.querySelector('dialog[open]');
+ const closeButton = screen.getByRole('button', { name: 'Close Menu' });
+ expect(dialog).toHaveAttribute('open');
+ await user.click(closeButton as HTMLButtonElement);
+
+ expect(nav).toBeInTheDocument();
+ expect(dialog).toBeInTheDocument();
+ await waitFor(() => {
+ expect(container.querySelector('dialog[open]')).not.toBeInTheDocument();
+ });
+ });
+
+ test('should close the drawer when clicking outside', () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // mobile size
+ const clickOutsideHandler = jest.fn();
+ (useClickOutside as jest.Mock).mockImplementation((ref, handler) => {
+ clickOutsideHandler.mockImplementation(handler);
+ });
+
+ const { container } = render();
+ const dialog = container.querySelector('dialog[open]');
+ clickOutsideHandler();
+ expect(dialog).not.toBeInTheDocument();
+ });
+ test('should lock body scroll when drawer is open', () => {
+ (useScreenSize as jest.Mock).mockReturnValue(500); // Mobile view
+ render();
+
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+ fireEvent.click(hamburgerButton);
+
+ expect(useLockBody).toHaveBeenCalledWith(true, expect.anything());
+ });
+ test('FocusTrap traps focus within the drawer when it is open', async () => {
+ const user = userEvent.setup();
+ (useScreenSize as jest.Mock).mockReturnValue(450); // Mobile view
+ const { container } = render();
+
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+ fireEvent.click(hamburgerButton);
+
+ await waitFor(() => {
+ const dialog = container.querySelector('dialog');
+ expect(dialog).toBeInTheDocument();
+ const focusableElement = screen.getByText('Close Menu');
+ expect(focusableElement).toHaveFocus();
+ });
+ await user.tab();
+ await waitFor(() => {
+ const closeButton = screen.getByText('Home');
+ expect(closeButton).toHaveFocus();
+ });
+ await user.tab();
+ await waitFor(() => {
+ const closeButton = screen.getByText('About');
+ expect(closeButton).toHaveFocus();
+ });
+ // Navigating backward through the focus trap and looping back to the start
+ await user.tab();
+ await waitFor(() => {
+ const closeButton = screen.getByText('Close Menu');
+ expect(closeButton).toHaveFocus();
+ });
+ });
+ test('FocusTrap should deactivate when the drawer is closed', async () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // Mobile view
+ const user = userEvent.setup();
+ render();
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+ await user.click(hamburgerButton);
+ const closeButton = screen.getByText('Close Menu');
+ await user.click(closeButton);
+ await waitFor(() => {
+ expect(screen.getByText('Open Menu')).toHaveFocus();
+ });
+ });
+ test('Pressing Escape should close the drawer and deactivate FocusTrap', async () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // Mobile view
+ const user = userEvent.setup();
+ render();
+
+ const hamburgerButton = screen.getByRole('button', { name: 'Open Menu' });
+ await user.click(hamburgerButton);
+
+ fireEvent.keyDown(document, { key: 'Escape', code: 'Escape' });
+ await waitFor(() => {
+ expect(screen.getByRole('button', { name: 'Open Menu' })).toHaveFocus();
+ });
+ });
+ test('should render the Navigation list on mobile view', () => {
+ (useScreenSize as jest.Mock).mockReturnValue(450); // mobile size
+ const { container } = render();
+ const nav = container.querySelector('nav');
+ const navList = nav?.querySelector(':only-child');
+ const navListItems = navList?.querySelectorAll('li a');
+
+ navListItems?.forEach((item) => {
+ expect(item).toBeInTheDocument();
+ expect(item).toHaveAttribute('href');
+ expect(item).toHaveTextContent(/Home|About/);
+ });
+ expect(navList).toBeInTheDocument();
+ expect(nav).toBeInTheDocument();
+ });
+});
diff --git a/packages/ui/tests/__mocks__/tabbable.js b/packages/ui/tests/__mocks__/tabbable.js
new file mode 100644
index 00000000..96eb49eb
--- /dev/null
+++ b/packages/ui/tests/__mocks__/tabbable.js
@@ -0,0 +1,11 @@
+// https://github.com/focus-trap/tabbable?tab=readme-ov-file#testing-in-jsdom
+const lib = jest.requireActual('tabbable');
+const tabbable = {
+ ...lib,
+ tabbable: (node, options) => lib.tabbable(node, { ...options, displayCheck: 'none' }),
+ focusable: (node, options) => lib.focusable(node, { ...options, displayCheck: 'none' }),
+ isFocusable: (node, options) => lib.isFocusable(node, { ...options, displayCheck: 'none' }),
+ isTabbable: (node, options) => lib.isTabbable(node, { ...options, displayCheck: 'none' }),
+};
+// eslint-disable-next-line no-undef
+module.exports = tabbable;
diff --git a/packages/ui/tsconfig.test.json b/packages/ui/tsconfig.test.json
index 2393929b..f86f7646 100644
--- a/packages/ui/tsconfig.test.json
+++ b/packages/ui/tsconfig.test.json
@@ -1,5 +1,5 @@
{
"extends": "./tsconfig.json",
- "include": ["./jest.config.ts", "**/*.test.ts", "**/*.test.tsx"],
+ "include": ["./jest.config.ts", "**/*.test.ts", "**/*.test.tsx", "./tests/__mocks__/tabbable.js"],
"exclude": ["**/node_modules/*"]
}
diff --git a/yarn.lock b/yarn.lock
index fcdd704c..9f373616 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6673,6 +6673,11 @@
"@testing-library/dom" "^10.0.0"
"@types/react-dom" "^18.0.0"
+"@testing-library/user-event@14.5.2":
+ version "14.5.2"
+ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd"
+ integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==
+
"@tiptap/core@2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.2.4.tgz#6f957678eb733e70b9282fb5098d284a77bd09a3"