UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

313 lines (261 loc) 12.3 kB
import '@testing-library/jest-dom' import { fireEvent } from '@testing-library/dom' import { axe, toHaveNoViolations } from 'jest-axe' import { vi } from 'vitest' import { createElementTest, BaseTestConfig } from '../../tests/test-framework' import { CustomElementFor } from '../../tests/component-registry' import './searchinput' expect.extend(toHaveNoViolations) interface SearchInputTestConfig extends BaseTestConfig { id?: string name?: string appearance?: 'local' | 'local-with-button' | 'global' label?: string placeholder?: string disabled?: boolean fullwidth?: boolean action?: string method?: 'get' | 'post' | 'dialog' value?: string } const createSearchInputTest = async (config: SearchInputTestConfig = {}) => { const { container, element } = await createElementTest< CustomElementFor<'pkt-searchinput'>, SearchInputTestConfig >('pkt-searchinput', { id: 'test-searchinput', ...config, }) return { container, searchinput: element, } } afterEach(() => { document.body.innerHTML = '' }) describe('PktSearchInput', () => { describe('wrapper', () => { test('renders form when action exists', async () => { const { searchinput } = await createSearchInputTest({ action: '/search', method: 'get' }) const wrapper = searchinput.querySelector('.pkt-searchinput') expect(wrapper?.tagName.toLowerCase()).toBe('form') expect(wrapper).toHaveAttribute('role', 'search') expect(wrapper).toHaveAttribute('action', '/search') expect(wrapper).toHaveAttribute('method', 'get') }) test('renders div when action does not exist', async () => { const { searchinput } = await createSearchInputTest() const wrapper = searchinput.querySelector('.pkt-searchinput') expect(wrapper?.tagName.toLowerCase()).toBe('div') expect(wrapper).toHaveAttribute('role', 'search') expect(searchinput.querySelector('form')).toBeNull() }) }) describe('label', () => { test('renders label when label exists', async () => { const { searchinput } = await createSearchInputTest({ label: 'Test Label' }) await searchinput.updateComplete const label = searchinput.querySelector('label.pkt-inputwrapper__label') expect(label).toBeInTheDocument() expect(label?.textContent).toContain('Test Label') expect(label).toHaveAttribute('for', 'test-searchinput') }) test('does not render label when label is absent', async () => { const { searchinput } = await createSearchInputTest() await searchinput.updateComplete expect(searchinput.querySelector('label')).toBeNull() }) }) describe('input attributes', () => { test('input has expected type, name, id, placeholder, disabled', async () => { const { searchinput } = await createSearchInputTest({ id: 'my-search', name: 'q', placeholder: 'Finn…', disabled: true, }) await searchinput.updateComplete const input = searchinput.querySelector('input') as HTMLInputElement expect(input.type).toBe('search') expect(input.id).toBe('my-search') expect(input.name).toBe('q') expect(input.placeholder).toBe('Finn…') expect(input.disabled).toBe(true) }) test('name defaults to id when name is not set', async () => { const { searchinput } = await createSearchInputTest({ id: 'search-field' }) await searchinput.updateComplete expect((searchinput.querySelector('input') as HTMLInputElement).name).toBe('search-field') }) test('placeholder defaults to Søk…', async () => { const { searchinput } = await createSearchInputTest() await searchinput.updateComplete expect((searchinput.querySelector('input') as HTMLInputElement).placeholder).toBe('Søk…') }) }) describe('appearance and fullwidth classes', () => { test('applies appearance modifier and fullwidth on wrapper', async () => { const { searchinput } = await createSearchInputTest({ appearance: 'global', fullwidth: true, }) await searchinput.updateComplete const wrapper = searchinput.querySelector('.pkt-searchinput') expect(wrapper?.classList.contains('pkt-searchinput--global')).toBe(true) expect(wrapper?.classList.contains('pkt-searchinput--fullwidth')).toBe(true) }) test('local uses pkt-input__container; local-with-button uses pkt-searchinput__field', async () => { const { searchinput: localEl } = await createSearchInputTest({ appearance: 'local' }) await localEl.updateComplete expect(localEl.querySelector('.pkt-input__container')).toBeTruthy() expect(localEl.querySelector('.pkt-searchinput__field')).toBeNull() const { searchinput: lwb } = await createSearchInputTest({ appearance: 'local-with-button', }) await lwb.updateComplete expect(lwb.querySelector('.pkt-searchinput__field')).toBeTruthy() expect(lwb.querySelector('.pkt-input__container')).toBeNull() }) }) describe('pkt-search', () => { test('fires on Enter with detail { value }', async () => { const { searchinput } = await createSearchInputTest() const searchSpy = vi.fn() searchinput.addEventListener('pkt-search', searchSpy) const input = searchinput.querySelector('input') as HTMLInputElement fireEvent.input(input, { target: { value: 'oslo' } }) fireEvent.keyDown(input, { key: 'Enter' }) expect(searchSpy).toHaveBeenCalledTimes(1) expect(searchSpy.mock.calls[0][0].detail).toEqual({ value: 'oslo' }) }) test('fires on search button click with detail { value }', async () => { const { searchinput } = await createSearchInputTest({ value: 'kommune' }) await searchinput.updateComplete const searchSpy = vi.fn() searchinput.addEventListener('pkt-search', searchSpy) const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement fireEvent.click(button) expect(searchSpy).toHaveBeenCalledTimes(1) expect(searchSpy.mock.calls[0][0].detail).toEqual({ value: 'kommune' }) }) }) describe('search submit button classes', () => { test('local: tertiary, icon-only, pkt-input-icon', async () => { const { searchinput } = await createSearchInputTest({ appearance: 'local' }) await searchinput.updateComplete const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement expect(button.tagName.toLowerCase()).toBe('button') expect(button.classList.contains('pkt-input-icon')).toBe(true) expect(button.classList.contains('pkt-btn--tertiary')).toBe(true) expect(button.classList.contains('pkt-btn--icon-only')).toBe(true) expect(button.classList.contains('pkt-btn--medium')).toBe(true) }) test('global: primary, yellow', async () => { const { searchinput } = await createSearchInputTest({ appearance: 'global' }) await searchinput.updateComplete const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement expect(button.classList.contains('pkt-btn--primary')).toBe(true) expect(button.classList.contains('pkt-btn--yellow')).toBe(true) expect(button.classList.contains('pkt-input-icon')).toBe(false) }) }) describe('suggestions', () => { test('renders from suggestions property without mutating the array', async () => { const suggestions = [ { title: 'A', text: 'tekst', href: 'https://oslo.kommune.no' }, { title: 'B', text: 'tekst 2' }, ] const snapshot = JSON.stringify(suggestions) const { searchinput } = await createSearchInputTest() searchinput.suggestions = suggestions await searchinput.updateComplete expect(searchinput.querySelectorAll('.pkt-searchinput__suggestion')).toHaveLength(2) expect(JSON.stringify(suggestions)).toBe(snapshot) }) test('suggestion with href renders as link', async () => { const { searchinput } = await createSearchInputTest() searchinput.suggestions = [{ title: 'Lenke', href: 'https://oslo.kommune.no' }] await searchinput.updateComplete const link = searchinput.querySelector('a.pkt-searchinput__suggestion') expect(link).toBeTruthy() expect(link?.getAttribute('href')).toBe('https://oslo.kommune.no') }) test('suggestion without href renders as button type="button"', async () => { const { searchinput } = await createSearchInputTest() searchinput.suggestions = [{ title: 'Knapp', text: 'beskrivelse' }] await searchinput.updateComplete const btn = searchinput.querySelector('button.pkt-searchinput__suggestion') expect(btn).toBeTruthy() expect(btn?.getAttribute('type')).toBe('button') }) test('nonInteractive suggestion renders as div and does not emit pkt-suggestion-click', async () => { const suggestion = { text: 'Ingen resultater', nonInteractive: true } const { searchinput } = await createSearchInputTest() searchinput.suggestions = [suggestion] await searchinput.updateComplete expect(searchinput.querySelector('div.pkt-searchinput__suggestion')).toBeTruthy() expect(searchinput.querySelector('button.pkt-searchinput__suggestion')).toBeNull() const spy = vi.fn() searchinput.addEventListener('pkt-suggestion-click', spy) const div = searchinput.querySelector('div.pkt-searchinput__suggestion') as HTMLDivElement fireEvent.click(div) expect(spy).not.toHaveBeenCalled() }) test('pkt-suggestion-click fires with detail { index, suggestion } on link click', async () => { const suggestion = { title: 'T', href: 'https://oslo.kommune.no' } const { searchinput } = await createSearchInputTest() searchinput.suggestions = [suggestion] await searchinput.updateComplete const spy = vi.fn() searchinput.addEventListener('pkt-suggestion-click', spy) searchinput.addEventListener('pkt-suggestion-click', (e: Event) => e.preventDefault()) const link = searchinput.querySelector('a.pkt-searchinput__suggestion') as HTMLAnchorElement fireEvent.click(link) expect(spy).toHaveBeenCalledTimes(1) const evt = spy.mock.calls[0][0] as CustomEvent expect(evt.detail).toEqual({ index: 0, suggestion }) expect(evt.cancelable).toBe(true) }) test('pkt-suggestion-click fires with detail { index, suggestion } on button click', async () => { const suggestion = { title: 'X', text: 'y' } const { searchinput } = await createSearchInputTest() searchinput.suggestions = [suggestion] await searchinput.updateComplete const spy = vi.fn() searchinput.addEventListener('pkt-suggestion-click', spy) const btn = searchinput.querySelector('button.pkt-searchinput__suggestion') as HTMLButtonElement fireEvent.click(btn) expect(spy).toHaveBeenCalledTimes(1) expect((spy.mock.calls[0][0] as CustomEvent).detail).toEqual({ index: 0, suggestion, }) }) test('canceling pkt-suggestion-click causes preventDefault on the anchor MouseEvent', async () => { const { searchinput } = await createSearchInputTest() searchinput.suggestions = [{ title: 'Resultat', href: 'https://oslo.kommune.no' }] await searchinput.updateComplete searchinput.addEventListener('pkt-suggestion-click', (e: Event) => e.preventDefault()) const link = searchinput.querySelector('a.pkt-searchinput__suggestion') as HTMLAnchorElement let seen: MouseEvent | undefined link.addEventListener('click', (e: MouseEvent) => { seen = e }) fireEvent.click(link) expect(seen).toBeDefined() expect(seen!.defaultPrevented).toBe(true) }) }) describe('accessibility', () => { test('has no axe violations with label and suggestions', async () => { const { searchinput } = await createSearchInputTest({ label: 'Søk etter noe', }) searchinput.suggestions = [{ title: 'Resultat', text: 'Eksempeltekst' }] await searchinput.updateComplete const results = await axe(searchinput) expect(results).toHaveNoViolations() }) }) })