UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

209 lines (167 loc) 8.25 kB
import '@testing-library/jest-dom' import { axe, toHaveNoViolations } from 'jest-axe' import { fireEvent } from '@testing-library/dom' import { vi } from 'vitest' import { createElementTest, BaseTestConfig } from '../../tests/test-framework' import type { CustomElementFor } from '../../tests/component-registry' import './breadcrumbs' expect.extend(toHaveNoViolations) interface BreadcrumbsTestConfig extends BaseTestConfig { breadcrumbs?: { text: string; href: string }[] } const defaultBreadcrumbs = [ { text: 'Hjem', href: '/' }, { text: 'Produkter', href: '/produkter' }, { text: 'Detaljer', href: '/produkter/detaljer' }, ] const createBreadcrumbsTest = async (config: BreadcrumbsTestConfig = {}) => { const { container, element } = await createElementTest< CustomElementFor<'pkt-breadcrumbs'>, BreadcrumbsTestConfig >('pkt-breadcrumbs', config) if (config.breadcrumbs) { element.breadcrumbs = config.breadcrumbs await element.updateComplete } return { container, breadcrumbs: element } } afterEach(() => { document.body.innerHTML = '' }) describe('PktBreadcrumbs', () => { describe('Rendering', () => { test('renders without errors', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) expect(breadcrumbs).toBeInTheDocument() }) test('renders all breadcrumb items', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const items = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__item') expect(items).toHaveLength(3) }) test('renders nothing when breadcrumbs array is empty', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: [] }) const nav = breadcrumbs.querySelector('nav') expect(nav).not.toBeInTheDocument() }) test('renders correctly when breadcrumbs are set as JSON attribute', async () => { const container = document.createElement('div') document.body.appendChild(container) container.innerHTML = `<pkt-breadcrumbs breadcrumbs='${JSON.stringify(defaultBreadcrumbs)}'></pkt-breadcrumbs>` const element = container.querySelector('pkt-breadcrumbs') as CustomElementFor<'pkt-breadcrumbs'> await customElements.whenDefined('pkt-breadcrumbs') await element.updateComplete const items = element.querySelectorAll('.pkt-breadcrumbs__item') expect(items).toHaveLength(3) const links = element.querySelectorAll('.pkt-breadcrumbs__link') expect(links).toHaveLength(2) expect(links[0]).toHaveAttribute('href', '/') }) test('last item has aria-current and no link', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const items = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__item') const lastItem = items[items.length - 1] const span = lastItem.querySelector('[aria-current="true"]') expect(span).toBeInTheDocument() const link = lastItem.querySelector('a') expect(link).not.toBeInTheDocument() }) test('non-last items render as links with correct href', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const links = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__link') expect(links).toHaveLength(2) expect(links[0]).toHaveAttribute('href', '/') expect(links[1]).toHaveAttribute('href', '/produkter') }) test('renders breadcrumb text correctly', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const texts = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__text') // 3 desktop + 1 mobile back-link expect(texts).toHaveLength(4) expect(texts[0]).toHaveTextContent('Hjem') expect(texts[1]).toHaveTextContent('Produkter') expect(texts[2]).toHaveTextContent('Detaljer') }) test('renders desktop list with correct classes', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const list = breadcrumbs.querySelector('.pkt-breadcrumbs--desktop') expect(list).toBeInTheDocument() }) test('renders mobile back-link pointing to second-to-last item', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const mobileLink = breadcrumbs.querySelector('.pkt-breadcrumbs--mobile') expect(mobileLink).toBeInTheDocument() expect(mobileLink).toHaveAttribute('href', '/produkter') expect(mobileLink).toHaveTextContent('Produkter') }) test('renders chevron-right icons on desktop links', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const desktopIcons = breadcrumbs.querySelectorAll( '.pkt-breadcrumbs--desktop pkt-icon[name="chevron-thin-right"]', ) expect(desktopIcons).toHaveLength(2) }) test('renders chevron-left icon on mobile back-link', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const mobileIcon = breadcrumbs.querySelector( '.pkt-breadcrumbs--mobile pkt-icon[name="chevron-thin-left"]', ) expect(mobileIcon).toBeInTheDocument() }) }) describe('Events', () => { test('dispatches navigate event on link click', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const navigateSpy = vi.fn() breadcrumbs.addEventListener('navigate', navigateSpy) const link = breadcrumbs.querySelector('.pkt-breadcrumbs__link') as HTMLAnchorElement fireEvent.click(link) expect(navigateSpy).toHaveBeenCalledTimes(1) expect(navigateSpy.mock.calls[0][0].detail.item).toEqual({ text: 'Hjem', href: '/' }) expect(navigateSpy.mock.calls[0][0].detail.index).toBe(0) }) test('dispatches navigate event on mobile back-link click', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const navigateSpy = vi.fn() breadcrumbs.addEventListener('navigate', navigateSpy) const mobileLink = breadcrumbs.querySelector('.pkt-breadcrumbs--mobile') as HTMLAnchorElement fireEvent.click(mobileLink) expect(navigateSpy).toHaveBeenCalledTimes(1) expect(navigateSpy.mock.calls[0][0].detail.item).toEqual({ text: 'Produkter', href: '/produkter', }) expect(navigateSpy.mock.calls[0][0].detail.index).toBe(1) }) test('preventDefault on navigate event prevents default link behavior', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) breadcrumbs.addEventListener('navigate', (e: Event) => { e.preventDefault() }) const link = breadcrumbs.querySelector('.pkt-breadcrumbs__link') as HTMLAnchorElement const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }) const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault') link.dispatchEvent(clickEvent) expect(preventDefaultSpy).toHaveBeenCalled() }) }) describe('Accessibility', () => { test('has nav with aria-label', async () => { const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const nav = breadcrumbs.querySelector('nav') expect(nav).toHaveAttribute('aria-label', 'brødsmulemeny') }) test('has no WCAG violations', async () => { // Suppress jsdom "Not implemented: navigation" error triggered by axe-core const originalError = console.error console.error = (...args: unknown[]) => { if (typeof args[0] === 'string' && args[0].includes('Not implemented: navigation')) return originalError(...args) } const { container } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs }) const results = await axe(container) expect(results).toHaveNoViolations() console.error = originalError }) }) })