UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

544 lines (440 loc) 21.2 kB
import '@testing-library/jest-dom' import { fireEvent } from '@testing-library/dom' import './combobox' import { PktCombobox } from './combobox' import type { IPktComboboxOption } from './combobox' const waitForCustomElements = async () => { await customElements.whenDefined('pkt-combobox') } const createCombobox = async (comboboxProps = '') => { const container = document.createElement('div') container.innerHTML = ` <pkt-combobox ${comboboxProps}></pkt-combobox> ` document.body.appendChild(container) await waitForCustomElements() return container } // Use a function to return fresh objects each time, preventing mutation leaks between tests const getDefaultOptions = (): IPktComboboxOption[] => [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, { value: 'cherry', label: 'Cherry' }, { value: 'date', label: 'Date' }, ] const openAndWaitForListbox = async (combobox: PktCombobox) => { const arrowButton = combobox.querySelector('.pkt-combobox__input') fireEvent.click(arrowButton!) await combobox.updateComplete const listbox = combobox.querySelector('pkt-listbox') as any await listbox?.updateComplete } afterEach(() => { document.body.innerHTML = '' }) describe('PktCombobox', () => { describe('Single selection', () => { test('selects a value via toggleValue', async () => { const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_value']).toEqual(['apple']) }) test('replaces current selection when selecting a new value', async () => { const container = await createCombobox('id="test" name="test" label="Test" value="apple"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete expect(combobox['_value']).toEqual(['apple']) ;(combobox as any).toggleValue('banana') await combobox.updateComplete expect(combobox['_value']).toEqual(['banana']) }) test('closes dropdown after selecting in single mode', async () => { const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete await openAndWaitForListbox(combobox) expect(combobox['_isOptionsOpen']).toBe(true) ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(false) }) test('deselects value when toggling already selected option', async () => { const container = await createCombobox('id="test" name="test" label="Test" value="apple"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) test('displays selected value as text in single mode', async () => { const container = await createCombobox('id="test" name="test" label="Test" value="apple"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const valueSpan = combobox.querySelector('.pkt-combobox__value') expect(valueSpan?.textContent?.trim()).toBe('Apple') }) test('renders value as tag with tagSkinColor in multiple mode', async () => { const optionsWithTags: IPktComboboxOption[] = [ { value: 'red', label: 'Red', tagSkinColor: 'red' }, { value: 'blue', label: 'Blue', tagSkinColor: 'blue' }, ] const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="red"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = optionsWithTags await combobox.updateComplete const tag = combobox.querySelector('pkt-tag') expect(tag).toBeInTheDocument() }) test('selects option when clicking it in the open dropdown', async () => { const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete await openAndWaitForListbox(combobox) const listbox = combobox.querySelector('pkt-listbox') as any await listbox?.updateComplete const option = combobox.querySelector('.pkt-listbox__option') expect(option).toBeInTheDocument() fireEvent.click(option!) await combobox.updateComplete expect(combobox['_value']).toEqual(['apple']) }) }) describe('Multiple selection', () => { test('selects multiple values', async () => { const container = await createCombobox('id="test" name="test" label="Test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete // Select first value ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_value']).toContain('apple') // Select second value ;(combobox as any).toggleValue('banana') await combobox.updateComplete expect(combobox['_value']).toContain('banana') expect(combobox['_value'].length).toBe(2) }) test('keeps dropdown open after selection in multiple mode', async () => { const container = await createCombobox('id="test" name="test" label="Test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete await openAndWaitForListbox(combobox) ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(true) }) test('renders selected values as tags in multiple mode', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const tags = combobox.querySelectorAll('pkt-tag') expect(tags.length).toBe(2) }) test('removes a selected value by clicking its tag close button', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const closeButtons = combobox.querySelectorAll('pkt-tag .pkt-tag__close-btn') expect(closeButtons.length).toBe(2) fireEvent.click(closeButtons[0]) await combobox.updateComplete expect(combobox['_value']).toEqual(['banana']) }) test('deselects value when toggling already selected option in multiple mode', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_value']).toEqual(['banana']) }) test('renders tags outside when tagPlacement is outside', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple tag-placement="outside" value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside') expect(outsideTags).toBeInTheDocument() const tags = outsideTags?.querySelectorAll('pkt-tag') expect(tags?.length).toBe(2) }) }) describe('Maxlength enforcement', () => { test('prevents selection beyond maxlength', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('cherry') await combobox.updateComplete expect(combobox['_value']).toEqual(['apple', 'banana']) expect(combobox['_value'].length).toBeLessThanOrEqual(2) }) test('shows max reached message when trying to exceed maxlength', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('cherry') await combobox.updateComplete expect(combobox['_userInfoMessage']).toBe('Maks antall valg nådd') }) test('allows deselection when at maxlength', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(combobox['_value']).toEqual(['banana']) }) }) describe('Disabled options', () => { test('does not select disabled options', async () => { const optionsWithDisabled: IPktComboboxOption[] = [ { value: 'enabled', label: 'Enabled' }, { value: 'disabled', label: 'Disabled', disabled: true }, ] const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = optionsWithDisabled await combobox.updateComplete ;(combobox as any).toggleValue('disabled') await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) }) describe('User input (custom values)', () => { test('adds custom value in single-select mode', async () => { const container = await createCombobox('id="test" name="test" label="Test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const input = combobox.querySelector('input[type="text"]') as HTMLInputElement fireEvent.focus(input) await combobox.updateComplete input.value = 'CustomFruit' fireEvent.input(input, { target: { value: 'CustomFruit' } }) await combobox.updateComplete fireEvent.keyDown(input, { key: 'Enter' }) await combobox.updateComplete expect(combobox['_value']).toContain('CustomFruit') expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true) }) test('adds custom value in multiple-select mode', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" allow-user-input multiple', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const input = combobox.querySelector('input[type="text"]') as HTMLInputElement fireEvent.focus(input) await combobox.updateComplete input.value = 'CustomFruit' fireEvent.input(input, { target: { value: 'CustomFruit' } }) await combobox.updateComplete fireEvent.keyDown(input, { key: 'Enter' }) await combobox.updateComplete expect(combobox['_value']).toContain('CustomFruit') expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true) }) test('removes user-added option when deselected via removeSelected', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" allow-user-input multiple', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).addNewUserValue('CustomFruit') await combobox.updateComplete expect(combobox['_value']).toContain('CustomFruit') expect(combobox.options.some((o) => o.value === 'CustomFruit')).toBe(true) ;(combobox as any).removeSelected('CustomFruit') await combobox.updateComplete expect(combobox['_value']).not.toContain('CustomFruit') expect(combobox.options.some((o) => o.value === 'CustomFruit')).toBe(false) }) test('preserves user-added options when options array is replaced externally', async () => { const container = await createCombobox('id="test" name="test" label="Test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).addNewUserValue('CustomFruit') await combobox.updateComplete combobox.options = [{ value: 'newOption', label: 'New Option' }] await combobox.updateComplete expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true) expect(combobox.options.some((o) => o.value === 'newOption')).toBe(true) }) test('does not add empty custom value', async () => { const container = await createCombobox('id="test" name="test" label="Test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete ;(combobox as any).addNewUserValue('') await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) test('does not add whitespace-only custom value', async () => { const container = await createCombobox('id="test" name="test" label="Test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete ;(combobox as any).addNewUserValue(' ') await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) }) describe('Select all / clear all', () => { test('selects all options via addAllOptions', async () => { const container = await createCombobox('id="test" name="test" label="Test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete ;(combobox as any).addAllOptions() await combobox.updateComplete expect(combobox['_value'].length).toBe(4) }) test('clears all selections by setting value to empty', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete expect(combobox['_value']).toEqual(['apple', 'banana']) combobox.value = [] await combobox.updateComplete // Allow cascading updates to settle await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) test('handles select-all event from listbox', async () => { const container = await createCombobox('id="test" name="test" label="Test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete const listbox = combobox.querySelector('pkt-listbox') fireEvent(listbox!, new CustomEvent('select-all')) await combobox.updateComplete expect(combobox['_value'].length).toBe(4) }) test('handles deselect-all event from listbox', async () => { const container = await createCombobox( 'id="test" name="test" label="Test" multiple value="apple,banana"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete expect(combobox['_value']).toEqual(['apple', 'banana']) // Set value directly via the public property for reliable clearing combobox.value = [] await combobox.updateComplete await combobox.updateComplete expect(combobox['_value']).toEqual([]) }) }) describe('Value change events', () => { test('dispatches value-change event on selection', async () => { const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete let valueChangeEvent: CustomEvent | null = null combobox.addEventListener('value-change', (e: Event) => { valueChangeEvent = e as CustomEvent }) ;(combobox as any).toggleValue('apple') await combobox.updateComplete expect(valueChangeEvent).toBeTruthy() }) test('dispatches value-change with array for multiple mode', async () => { const container = await createCombobox('id="test" name="test" label="Test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete let valueChangeDetail: any = null combobox.addEventListener('value-change', (e: Event) => { valueChangeDetail = (e as CustomEvent).detail }) combobox.value = ['apple', 'banana'] await combobox.updateComplete expect(valueChangeDetail).toEqual(['apple', 'banana']) }) test('dispatches value-change with string for single mode', async () => { const container = await createCombobox('id="test" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = getDefaultOptions() await combobox.updateComplete let valueChangeDetail: any = null combobox.addEventListener('value-change', (e: Event) => { valueChangeDetail = (e as CustomEvent).detail }) combobox.value = 'apple' await combobox.updateComplete expect(valueChangeDetail).toBe('apple') }) }) describe('displayValueAs modes', () => { test('displays value using label by default', async () => { const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }] const container = await createCombobox('id="test" name="test" label="Test" value="no"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = options await combobox.updateComplete const valueEl = combobox.querySelector('.pkt-combobox__value') expect(valueEl?.textContent?.trim()).toBe('Norway') }) test('displays value using value when displayValueAs is value', async () => { const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }] const container = await createCombobox( 'id="test" name="test" label="Test" value="no" display-value-as="value"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = options await combobox.updateComplete const valueEl = combobox.querySelector('.pkt-combobox__value') expect(valueEl?.textContent?.trim()).toBe('no') }) test('displays prefix and value when displayValueAs is prefixAndValue', async () => { const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }] const container = await createCombobox( 'id="test" name="test" label="Test" value="no" display-value-as="prefixAndValue"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = options await combobox.updateComplete const valueEl = combobox.querySelector('.pkt-combobox__value') expect(valueEl?.textContent?.trim()).toBe('NO no') }) }) })