UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

809 lines (617 loc) 30.8 kB
import '@testing-library/jest-dom' import { axe, toHaveNoViolations } from 'jest-axe' import { fireEvent } from '@testing-library/dom' import { vi } from 'vitest' expect.extend(toHaveNoViolations) import './combobox' import { PktCombobox } from './combobox' import type { IPktComboboxOption } from './combobox' const waitForCustomElements = async () => { await customElements.whenDefined('pkt-combobox') } // Helper function to create combobox markup const createCombobox = async (comboboxProps = '', options = '') => { const container = document.createElement('div') container.innerHTML = ` <pkt-combobox ${comboboxProps}> ${options} </pkt-combobox> ` document.body.appendChild(container) await waitForCustomElements() return container } // Helper function to create options const createOptions = (optionData: { value: string; label?: string; disabled?: boolean }[]) => { return optionData .map( (opt) => `<option value="${opt.value}" ${opt.disabled ? 'disabled' : ''}>${opt.label || opt.value}</option>`, ) .join('') } // Cleanup after each test afterEach(() => { document.body.innerHTML = '' }) describe('PktCombobox', () => { describe('Rendering and basic functionality', () => { test('renders without errors', async () => { const container = await createCombobox('id="test-combobox" name="test" label="Test Combobox"') const combobox = container.querySelector('pkt-combobox') as PktCombobox expect(combobox).toBeInTheDocument() await combobox.updateComplete expect(combobox).toBeTruthy() const wrapper = combobox.querySelector('pkt-input-wrapper') const comboboxDiv = combobox.querySelector('.pkt-combobox') expect(wrapper).toBeInTheDocument() expect(comboboxDiv).toBeInTheDocument() }) test('renders with correct structure', async () => { const container = await createCombobox('id="test" name="test" label="Test Label"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const wrapper = combobox.querySelector('pkt-input-wrapper') const comboboxDiv = wrapper?.querySelector('.pkt-combobox') const inputDiv = comboboxDiv?.querySelector('.pkt-combobox__input') const arrowIcon = inputDiv?.querySelector('.pkt-combobox__arrow-icon') const listbox = comboboxDiv?.querySelector('pkt-listbox') expect(wrapper).toBeInTheDocument() expect(comboboxDiv).toBeInTheDocument() expect(inputDiv).toBeInTheDocument() expect(arrowIcon).toBeInTheDocument() expect(listbox).toBeInTheDocument() }) test('renders select-only combobox with correct ARIA attributes', async () => { const container = await createCombobox('id="test-arrow" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const comboboxInput = combobox.querySelector('.pkt-combobox__input') const icon = comboboxInput?.querySelector('pkt-icon') expect(comboboxInput?.getAttribute('id')).toBe('test-arrow-combobox') expect(comboboxInput?.getAttribute('aria-expanded')).toBe('false') expect(comboboxInput?.getAttribute('aria-controls')).toBe('test-arrow-listbox') expect(comboboxInput?.getAttribute('aria-haspopup')).toBe('listbox') expect(comboboxInput?.getAttribute('aria-labelledby')).toBe('test-arrow-combobox-label') expect(comboboxInput?.getAttribute('role')).toBe('combobox') expect(icon?.getAttribute('name')).toBe('chevron-thin-down') }) }) describe('Properties and attributes', () => { test('applies default properties correctly', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.value).toBe('') expect(combobox.allowUserInput).toBe(false) expect(combobox.typeahead).toBe(false) expect(combobox.includeSearch).toBe(false) expect(combobox.multiple).toBe(false) expect(combobox.disabled).toBe(false) expect(combobox.options).toEqual([]) expect(combobox.tagPlacement).toBe(null) expect(combobox.maxlength).toBe(null) expect(combobox.displayValueAs).toBe('label') }) test('handles multiple property correctly', async () => { const container = await createCombobox('id="test" name="test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.multiple).toBe(true) const listbox = combobox.querySelector('pkt-listbox') expect(listbox?.hasAttribute('is-multi-select')).toBe(true) }) test('handles allowUserInput property correctly', async () => { const container = await createCombobox('id="test" name="test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.allowUserInput).toBe(true) const input = combobox.querySelector('input[type="text"]') expect(input).toBeInTheDocument() expect(input?.getAttribute('role')).toBe('combobox') }) test('handles typeahead property correctly', async () => { const container = await createCombobox('id="test" name="test" typeahead') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.typeahead).toBe(true) const input = combobox.querySelector('input[type="text"]') expect(input).toBeInTheDocument() expect(input?.getAttribute('aria-autocomplete')).toBe('both') }) test('handles includeSearch property correctly', async () => { const container = await createCombobox('id="test" name="test" include-search') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.includeSearch).toBe(true) const listbox = combobox.querySelector('pkt-listbox') expect(listbox?.hasAttribute('include-search')).toBe(true) }) test('handles disabled property correctly', async () => { const container = await createCombobox('id="test" name="test" disabled') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.disabled).toBe(true) const comboboxInput = combobox.querySelector('.pkt-combobox__input') expect(comboboxInput).toHaveClass('pkt-combobox__input--disabled') expect(comboboxInput?.getAttribute('tabindex')).toBe('-1') }) test('handles fullwidth property correctly', async () => { const container = await createCombobox('id="test" name="test" fullwidth') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.fullwidth).toBe(true) const inputDiv = combobox.querySelector('.pkt-combobox__input') expect(inputDiv).toHaveClass('pkt-combobox__input--fullwidth') }) }) describe('Options handling', () => { test('handles options provided via property', async () => { const testOptions: IPktComboboxOption[] = [ { value: 'option1', label: 'Option 1' }, { value: 'option2', label: 'Option 2' }, { value: 'option3', label: 'Option 3', disabled: true }, ] const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox // Set options via property combobox.options = testOptions await combobox.updateComplete // Ensure listbox has also updated const listbox = combobox.querySelector('pkt-listbox') as any await listbox?.updateComplete expect(combobox.options).toHaveLength(3) expect(combobox.options[0].value).toBe('option1') expect(combobox.options[0].label).toBe('Option 1') expect(combobox.options[2].disabled).toBe(true) expect(listbox?.options).toEqual( expect.arrayContaining([expect.objectContaining({ value: 'option1', label: 'Option 1' })]), ) }) test('handles options provided via slot', async () => { const optionsMarkup = createOptions([ { value: 'value1', label: 'Label 1' }, { value: 'value2', label: 'Label 2' }, ]) const container = await createCombobox('id="test" name="test"', optionsMarkup) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete // Options should be extracted from slot expect(combobox.options.length).toBeGreaterThan(0) expect(combobox.options.some((opt) => opt.value === 'value1')).toBe(true) expect(combobox.options.some((opt) => opt.value === 'value2')).toBe(true) }) test('handles defaultOptions property', async () => { const defaultOptions: IPktComboboxOption[] = [ { value: 'default1', label: 'Default 1' }, { value: 'default2', label: 'Default 2' }, ] const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.defaultOptions = defaultOptions await combobox.updateComplete expect(combobox.options).toHaveLength(2) expect(combobox.options[0].value).toBe('default1') expect(combobox.options[1].value).toBe('default2') }) test('preserves userAdded options when defaultOptions change', async () => { const container = await createCombobox('id="test" name="test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete // Add a user option const userOption: IPktComboboxOption = { value: 'user-added', label: 'User Added', userAdded: true, } combobox.options = [userOption] await combobox.updateComplete // Set default options const defaultOptions: IPktComboboxOption[] = [{ value: 'default1', label: 'Default 1' }] combobox.defaultOptions = defaultOptions await combobox.updateComplete // Should have both user-added and default options expect(combobox.options).toHaveLength(2) expect(combobox.options.some((opt) => opt.userAdded)).toBe(true) expect(combobox.options.some((opt) => opt.value === 'default1')).toBe(true) }) test('preserves userAdded option when parent replaces options by overwriting options property', async () => { const container = await createCombobox('id="test" name="test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete ;(combobox as any).addNewUserValue('userAdded') await combobox.updateComplete expect(combobox.options.some((o: any) => o.value === 'userAdded' && o.userAdded)).toBe(true) expect(combobox['_value']).toContain('userAdded') combobox.options = [{ value: 'external1', label: 'External 1' }] await combobox.updateComplete const hasUserAdded = combobox.options.some((o: any) => o.value === 'userAdded' && o.userAdded) const hasExternal = combobox.options.some((o: any) => o.value === 'external1') expect(hasExternal).toBe(true) expect(hasUserAdded).toBe(true) expect(combobox['_value']).toContain('userAdded') }) }) describe('Value handling', () => { test('handles single value correctly', async () => { const container = await createCombobox('id="test" name="test" value="test-value"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.value).toBe('test-value') // Internal _value should be an array with single item expect(combobox['_value']).toEqual(['test-value']) }) test('handles multiple values correctly', async () => { const container = await createCombobox('id="test" name="test" value="value1,value2" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.value).toBe('value1,value2') expect(combobox['_value']).toEqual(['value1', 'value2']) }) test('handles array value correctly', async () => { const container = await createCombobox('id="test" name="test" multiple') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.value = ['array1', 'array2'] await combobox.updateComplete expect(combobox['_value']).toEqual(['array1', 'array2']) }) test('limits to single value when not multiple', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.value = ['value1', 'value2'] await combobox.updateComplete // Should only keep the first value when not multiple expect(combobox['_value']).toEqual(['value1']) }) }) describe('Placeholder functionality', () => { test('shows placeholder when no value selected', async () => { const container = await createCombobox('id="test" name="test" placeholder="Select an option"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.placeholder).toBe('Select an option') const placeholder = combobox.querySelector('.pkt-combobox__placeholder') expect(placeholder).toBeInTheDocument() expect(placeholder?.textContent).toBe('Select an option') }) test('hides placeholder when value is selected', async () => { const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }] const container = await createCombobox( 'id="test" name="test" placeholder="Select an option" value="option1"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = testOptions await combobox.updateComplete const placeholder = combobox.querySelector('.pkt-combobox__placeholder') expect(placeholder).not.toBeInTheDocument() }) test('shows placeholder in multiple mode with outside tag placement', async () => { const container = await createCombobox( 'id="test" name="test" placeholder="Select options" multiple tag-placement="outside"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const placeholder = combobox.querySelector('.pkt-combobox__placeholder') expect(placeholder).toBeInTheDocument() expect(placeholder?.textContent).toBe('Select options') }) }) describe('Tag placement functionality', () => { test('renders tags inside input by default in multiple mode', async () => { const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }] const container = await createCombobox('id="test" name="test" multiple value="option1"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = testOptions await combobox.updateComplete const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside') expect(outsideTags).not.toBeInTheDocument() }) test('renders tags outside input when tagPlacement is outside', async () => { const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }] const container = await createCombobox( 'id="test" name="test" multiple tag-placement="outside" value="option1"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = testOptions await combobox.updateComplete const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside') expect(outsideTags).toBeInTheDocument() }) }) describe('Input field functionality', () => { test('renders hidden input when not allowUserInput or typeahead', async () => { const container = await createCombobox('id="test" name="test-name"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const hiddenInput = combobox.querySelector('input[type="hidden"]') const textInput = combobox.querySelector('input[type="text"]') expect(hiddenInput).toBeInTheDocument() expect(textInput).not.toBeInTheDocument() expect(hiddenInput?.getAttribute('id')).toBe('test-input') expect(hiddenInput?.getAttribute('name')).toBe('test-name-input') }) test('renders text input when allowUserInput is true', async () => { const container = await createCombobox('id="test" name="test-name" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') const hiddenInput = combobox.querySelector('input[type="hidden"]') expect(textInput).toBeInTheDocument() expect(hiddenInput).not.toBeInTheDocument() expect(textInput?.getAttribute('id')).toBe('test-input') expect(textInput?.getAttribute('name')).toBe('test-name-input') expect(textInput?.getAttribute('role')).toBe('combobox') expect(textInput?.getAttribute('aria-controls')).toBe('test-listbox') }) test('renders text input when typeahead is true', async () => { const container = await createCombobox('id="test" name="test" typeahead') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') expect(textInput).toBeInTheDocument() expect(textInput?.getAttribute('aria-autocomplete')).toBe('both') }) test('sets correct aria-autocomplete for allowUserInput', async () => { const container = await createCombobox('id="test" name="test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') expect(textInput?.getAttribute('aria-autocomplete')).toBe('list') }) }) describe('Dropdown functionality', () => { test('opens dropdown when arrow button is clicked', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const arrowButton = combobox.querySelector('.pkt-combobox__input') const inputDiv = combobox.querySelector('.pkt-combobox__input') expect(combobox['_isOptionsOpen']).toBe(false) expect(arrowButton?.getAttribute('aria-expanded')).toBe('false') expect(inputDiv).not.toHaveClass('pkt-combobox__input--open') // Click arrow to open fireEvent.click(arrowButton!) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(true) expect(arrowButton?.getAttribute('aria-expanded')).toBe('true') expect(inputDiv).toHaveClass('pkt-combobox__input--open') }) test('toggles dropdown state with multiple clicks', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const arrowButton = combobox.querySelector('.pkt-combobox__input') // Click to open fireEvent.click(arrowButton!) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(true) // Click to close fireEvent.click(arrowButton!) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(false) }) test('does not open when disabled', async () => { const container = await createCombobox('id="test" name="test" disabled') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const arrowButton = combobox.querySelector('.pkt-combobox__input') // Try to click when disabled fireEvent.click(arrowButton!) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(false) }) }) describe('Search functionality', () => { test('handles search input when includeSearch is true', async () => { const container = await createCombobox('id="test" name="test" include-search') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const listbox = combobox.querySelector('pkt-listbox') expect(listbox?.hasAttribute('include-search')).toBe(true) }) test('sets search placeholder correctly', async () => { const container = await createCombobox( 'id="test" name="test" include-search search-placeholder="Search items..."', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.searchPlaceholder).toBe('Search items...') const listbox = combobox.querySelector('pkt-listbox') as any expect(listbox?.searchPlaceholder).toBe('Search items...') }) }) describe('Keyboard navigation', () => { test('handles keyboard events on arrow button', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const arrowButton = combobox.querySelector('.pkt-combobox__input') // Test Enter key fireEvent.keyDown(arrowButton!, { key: 'Enter' }) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(true) // Test Space key fireEvent.keyDown(arrowButton!, { key: ' ' }) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(false) }) test('handles input focus for text inputs', async () => { const container = await createCombobox('id="test" name="test" allow-user-input') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') // Test focus event fireEvent.focus(textInput!) await combobox.updateComplete // Test blur event fireEvent.blur(textInput!) await combobox.updateComplete // Should not throw errors expect(combobox).toBeInTheDocument() }) }) describe('Max length functionality', () => { test('handles maxlength property correctly', async () => { const container = await createCombobox('id="test" name="test" multiple maxlength="3"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox.maxlength).toBe(3) const listbox = combobox.querySelector('pkt-listbox') as any expect(listbox?.maxLength).toBe(3) }) test('disables user input when max is reached', async () => { const testOptions: IPktComboboxOption[] = [ { value: 'option1', label: 'Option 1' }, { value: 'option2', label: 'Option 2' }, { value: 'option3', label: 'Option 3' }, ] const container = await createCombobox( 'id="test" name="test" multiple allow-user-input maxlength="2"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = testOptions combobox.value = ['option1', 'option2'] // Set to max await combobox.updateComplete const listbox = combobox.querySelector('pkt-listbox') as any await listbox?.updateComplete expect(listbox?.maxIsReached).toBe(true) }) }) describe('Error handling', () => { test('applies error styling when hasError is true', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.hasError = true await combobox.updateComplete const inputDiv = combobox.querySelector('.pkt-combobox__input') expect(inputDiv).toHaveClass('pkt-combobox__input--error') }) test('passes error state to input wrapper', async () => { const container = await createCombobox('id="test" name="test" error-message="Test error"') const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.hasError = true await combobox.updateComplete const wrapper = combobox.querySelector('pkt-input-wrapper') expect(wrapper?.hasAttribute('haserror')).toBe(true) }) }) describe('Accessibility', () => { test('has no accessibility violations', async () => { const container = await createCombobox( 'id="accessible-combobox" name="test" label="Accessible Combobox"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const results = await axe(combobox) expect(results).toHaveNoViolations() }) test('sets correct ARIA attributes on select-only combobox', async () => { const container = await createCombobox('id="test-aria" name="test" label="Test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const comboboxInput = combobox.querySelector('.pkt-combobox__input') expect(comboboxInput?.getAttribute('role')).toBe('combobox') expect(comboboxInput?.getAttribute('aria-controls')).toBe('test-aria-listbox') expect(comboboxInput?.getAttribute('aria-haspopup')).toBe('listbox') expect(comboboxInput?.getAttribute('aria-expanded')).toBe('false') expect(comboboxInput?.getAttribute('aria-labelledby')).toBe('test-aria-combobox-label') }) test('sets correct ARIA attributes on text input', async () => { const container = await createCombobox( 'id="test-input" name="test" label="Test Label" allow-user-input', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') expect(textInput?.getAttribute('role')).toBe('combobox') expect(textInput?.getAttribute('aria-controls')).toBe('test-input-listbox') expect(textInput?.getAttribute('aria-label')).toBe('Test Label') expect(textInput?.getAttribute('aria-autocomplete')).toBe('list') }) test('sets correct aria-activedescendant when value is selected', async () => { const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }] const container = await createCombobox( 'id="test" name="test" allow-user-input value="option1"', ) const combobox = container.querySelector('pkt-combobox') as PktCombobox combobox.options = testOptions await combobox.updateComplete const textInput = combobox.querySelector('input[type="text"]') expect(textInput?.getAttribute('aria-activedescendant')).toBe('test-listbox--1') }) }) describe('Event handling', () => { test('dispatches search event when search value changes', async () => { const container = await createCombobox('id="test" name="test" include-search') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const searchEventSpy = vi.fn() combobox.addEventListener('search', searchEventSpy) // Simulate search change combobox['_search'] = 'test search' await combobox.updateComplete expect(searchEventSpy).toHaveBeenCalledWith( expect.objectContaining({ detail: 'test search', }), ) }) test('handles listbox events correctly', async () => { const container = await createCombobox('id="test" name="test"') const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete const listbox = combobox.querySelector('pkt-listbox') // Test close-options event fireEvent(listbox!, new CustomEvent('close-options')) await combobox.updateComplete expect(combobox['_isOptionsOpen']).toBe(false) }) }) describe('Dynamic slot options', () => { test('picks up dynamically added <option> children', async () => { const container = await createCombobox( 'id="dynamic" name="dynamic"', createOptions([ { value: 'a', label: 'A' }, { value: 'b', label: 'B' }, ]), ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox['_options']).toHaveLength(2) // Add a new option element to the DOM const newOption = document.createElement('option') newOption.value = 'c' newOption.textContent = 'C' combobox.appendChild(newOption) // Wait for MutationObserver + Lit update await combobox.updateComplete await new Promise((r) => setTimeout(r, 0)) await combobox.updateComplete expect(combobox['_options']).toHaveLength(3) expect(combobox['_options'].some((o: IPktComboboxOption) => o.value === 'c')).toBe(true) }) test('picks up dynamically removed <option> children', async () => { const container = await createCombobox( 'id="dynamic" name="dynamic"', createOptions([ { value: 'a', label: 'A' }, { value: 'b', label: 'B' }, { value: 'c', label: 'C' }, ]), ) const combobox = container.querySelector('pkt-combobox') as PktCombobox await combobox.updateComplete expect(combobox['_options']).toHaveLength(3) // Remove the last option element const options = combobox.querySelectorAll('option') combobox.removeChild(options[options.length - 1]) // Wait for MutationObserver + Lit update await combobox.updateComplete await new Promise((r) => setTimeout(r, 0)) await combobox.updateComplete expect(combobox['_options']).toHaveLength(2) expect(combobox['_options'].some((o: IPktComboboxOption) => o.value === 'c')).toBe(false) }) }) })