@oslokommune/punkt-elements 13.4.1 → 13.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/{card-uccD6Pnv.cjs → card-BUITGoqX.cjs} +10 -10
- package/dist/{card-BI1NZONj.js → card-Dtw26f7i.js} +96 -76
- package/dist/checkbox-Gn7Wtk9h.cjs +31 -0
- package/dist/checkbox-ym7z6cpt.js +142 -0
- package/dist/{combobox-BhcqC30d.cjs → combobox-DjO0RMUB.cjs} +1 -1
- package/dist/{combobox-D9dGKWuZ.js → combobox-yE4aYhTi.js} +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/pkt-card.cjs +1 -1
- package/dist/pkt-card.js +1 -1
- package/dist/pkt-checkbox.cjs +1 -1
- package/dist/pkt-checkbox.js +1 -1
- package/dist/pkt-combobox.cjs +1 -1
- package/dist/pkt-combobox.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +3 -3
- package/package.json +2 -2
- package/src/components/card/card.test.ts +592 -0
- package/src/components/card/card.ts +24 -1
- package/src/components/checkbox/checkbox.test.ts +535 -0
- package/src/components/checkbox/checkbox.ts +44 -1
- package/src/components/combobox/combobox.test.ts +737 -0
- package/src/components/combobox/combobox.ts +1 -1
- package/dist/checkbox-CTRbpbye.js +0 -120
- package/dist/checkbox-wJ26voZd.cjs +0 -30
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
|
|
5
|
+
expect.extend(toHaveNoViolations)
|
|
6
|
+
|
|
7
|
+
import './combobox'
|
|
8
|
+
import { PktCombobox } from './combobox'
|
|
9
|
+
import type { IPktComboboxOption } from './combobox'
|
|
10
|
+
|
|
11
|
+
const waitForCustomElements = async () => {
|
|
12
|
+
await customElements.whenDefined('pkt-combobox')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Helper function to create combobox markup
|
|
16
|
+
const createCombobox = async (comboboxProps = '', options = '') => {
|
|
17
|
+
const container = document.createElement('div')
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<pkt-combobox ${comboboxProps}>
|
|
20
|
+
${options}
|
|
21
|
+
</pkt-combobox>
|
|
22
|
+
`
|
|
23
|
+
document.body.appendChild(container)
|
|
24
|
+
await waitForCustomElements()
|
|
25
|
+
return container
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Helper function to create options
|
|
29
|
+
const createOptions = (optionData: { value: string; label?: string; disabled?: boolean }[]) => {
|
|
30
|
+
return optionData
|
|
31
|
+
.map(
|
|
32
|
+
(opt) =>
|
|
33
|
+
`<option value="${opt.value}" ${opt.disabled ? 'disabled' : ''}>${opt.label || opt.value}</option>`,
|
|
34
|
+
)
|
|
35
|
+
.join('')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cleanup after each test
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
document.body.innerHTML = ''
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('PktCombobox', () => {
|
|
44
|
+
describe('Rendering and basic functionality', () => {
|
|
45
|
+
test('renders without errors', async () => {
|
|
46
|
+
const container = await createCombobox('id="test-combobox" name="test" label="Test Combobox"')
|
|
47
|
+
|
|
48
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
49
|
+
expect(combobox).toBeInTheDocument()
|
|
50
|
+
|
|
51
|
+
await combobox.updateComplete
|
|
52
|
+
expect(combobox).toBeTruthy()
|
|
53
|
+
|
|
54
|
+
const wrapper = combobox.querySelector('pkt-input-wrapper')
|
|
55
|
+
const comboboxDiv = combobox.querySelector('.pkt-combobox')
|
|
56
|
+
expect(wrapper).toBeInTheDocument()
|
|
57
|
+
expect(comboboxDiv).toBeInTheDocument()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('renders with correct structure', async () => {
|
|
61
|
+
const container = await createCombobox('id="test" name="test" label="Test Label"')
|
|
62
|
+
|
|
63
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
64
|
+
await combobox.updateComplete
|
|
65
|
+
|
|
66
|
+
const wrapper = combobox.querySelector('pkt-input-wrapper')
|
|
67
|
+
const comboboxDiv = wrapper?.querySelector('.pkt-combobox')
|
|
68
|
+
const inputDiv = comboboxDiv?.querySelector('.pkt-combobox__input')
|
|
69
|
+
const arrowButton = inputDiv?.querySelector('.pkt-combobox__arrow')
|
|
70
|
+
const listbox = comboboxDiv?.querySelector('pkt-listbox')
|
|
71
|
+
|
|
72
|
+
expect(wrapper).toBeInTheDocument()
|
|
73
|
+
expect(comboboxDiv).toBeInTheDocument()
|
|
74
|
+
expect(inputDiv).toBeInTheDocument()
|
|
75
|
+
expect(arrowButton).toBeInTheDocument()
|
|
76
|
+
expect(listbox).toBeInTheDocument()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('renders arrow button with correct attributes', async () => {
|
|
80
|
+
const container = await createCombobox('id="test-arrow" name="test" label="Test"')
|
|
81
|
+
|
|
82
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
83
|
+
await combobox.updateComplete
|
|
84
|
+
|
|
85
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
86
|
+
const icon = arrowButton?.querySelector('pkt-icon')
|
|
87
|
+
|
|
88
|
+
expect(arrowButton?.getAttribute('id')).toBe('test-arrow-arrow')
|
|
89
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
90
|
+
expect(arrowButton?.getAttribute('aria-controls')).toBe('test-arrow-listbox')
|
|
91
|
+
expect(arrowButton?.getAttribute('aria-haspopup')).toBe('listbox')
|
|
92
|
+
expect(arrowButton?.getAttribute('aria-label')).toBe('Åpne liste')
|
|
93
|
+
expect(arrowButton?.getAttribute('role')).toBe('button')
|
|
94
|
+
expect(icon?.getAttribute('name')).toBe('chevron-thin-down')
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('Properties and attributes', () => {
|
|
99
|
+
test('applies default properties correctly', async () => {
|
|
100
|
+
const container = await createCombobox('id="test" name="test"')
|
|
101
|
+
|
|
102
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
103
|
+
await combobox.updateComplete
|
|
104
|
+
|
|
105
|
+
expect(combobox.value).toBe(undefined)
|
|
106
|
+
expect(combobox.allowUserInput).toBe(false)
|
|
107
|
+
expect(combobox.typeahead).toBe(false)
|
|
108
|
+
expect(combobox.includeSearch).toBe(false)
|
|
109
|
+
expect(combobox.multiple).toBe(false)
|
|
110
|
+
expect(combobox.disabled).toBe(false)
|
|
111
|
+
expect(combobox.options).toEqual([])
|
|
112
|
+
expect(combobox.tagPlacement).toBe(null)
|
|
113
|
+
expect(combobox.maxlength).toBe(null)
|
|
114
|
+
expect(combobox.displayValueAs).toBe('label')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('handles multiple property correctly', async () => {
|
|
118
|
+
const container = await createCombobox('id="test" name="test" multiple')
|
|
119
|
+
|
|
120
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
121
|
+
await combobox.updateComplete
|
|
122
|
+
|
|
123
|
+
expect(combobox.multiple).toBe(true)
|
|
124
|
+
|
|
125
|
+
const listbox = combobox.querySelector('pkt-listbox')
|
|
126
|
+
expect(listbox?.hasAttribute('ismultiselect')).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('handles allowUserInput property correctly', async () => {
|
|
130
|
+
const container = await createCombobox('id="test" name="test" allowUserInput')
|
|
131
|
+
|
|
132
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
133
|
+
await combobox.updateComplete
|
|
134
|
+
|
|
135
|
+
expect(combobox.allowUserInput).toBe(true)
|
|
136
|
+
|
|
137
|
+
const input = combobox.querySelector('input[type="text"]')
|
|
138
|
+
expect(input).toBeInTheDocument()
|
|
139
|
+
expect(input?.getAttribute('role')).toBe('combobox')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('handles typeahead property correctly', async () => {
|
|
143
|
+
const container = await createCombobox('id="test" name="test" typeahead')
|
|
144
|
+
|
|
145
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
146
|
+
await combobox.updateComplete
|
|
147
|
+
|
|
148
|
+
expect(combobox.typeahead).toBe(true)
|
|
149
|
+
|
|
150
|
+
const input = combobox.querySelector('input[type="text"]')
|
|
151
|
+
expect(input).toBeInTheDocument()
|
|
152
|
+
expect(input?.getAttribute('aria-autocomplete')).toBe('both')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('handles includeSearch property correctly', async () => {
|
|
156
|
+
const container = await createCombobox('id="test" name="test" includeSearch')
|
|
157
|
+
|
|
158
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
159
|
+
await combobox.updateComplete
|
|
160
|
+
|
|
161
|
+
expect(combobox.includeSearch).toBe(true)
|
|
162
|
+
|
|
163
|
+
const listbox = combobox.querySelector('pkt-listbox')
|
|
164
|
+
expect(listbox?.hasAttribute('includesearch')).toBe(true)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('handles disabled property correctly', async () => {
|
|
168
|
+
const container = await createCombobox('id="test" name="test" disabled')
|
|
169
|
+
|
|
170
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
171
|
+
await combobox.updateComplete
|
|
172
|
+
|
|
173
|
+
expect(combobox.disabled).toBe(true)
|
|
174
|
+
|
|
175
|
+
const inputDiv = combobox.querySelector('.pkt-combobox__input')
|
|
176
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
177
|
+
expect(inputDiv).toHaveClass('pkt-combobox__input--disabled')
|
|
178
|
+
expect(arrowButton?.getAttribute('tabindex')).toBe('-1')
|
|
179
|
+
expect(arrowButton?.hasAttribute('disabled')).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('handles fullwidth property correctly', async () => {
|
|
183
|
+
const container = await createCombobox('id="test" name="test" fullwidth')
|
|
184
|
+
|
|
185
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
186
|
+
await combobox.updateComplete
|
|
187
|
+
|
|
188
|
+
expect(combobox.fullwidth).toBe(true)
|
|
189
|
+
|
|
190
|
+
const inputDiv = combobox.querySelector('.pkt-combobox__input')
|
|
191
|
+
expect(inputDiv).toHaveClass('pkt-combobox__input--fullwidth')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe('Options handling', () => {
|
|
196
|
+
test('handles options provided via property', async () => {
|
|
197
|
+
const testOptions: IPktComboboxOption[] = [
|
|
198
|
+
{ value: 'option1', label: 'Option 1' },
|
|
199
|
+
{ value: 'option2', label: 'Option 2' },
|
|
200
|
+
{ value: 'option3', label: 'Option 3', disabled: true },
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
const container = await createCombobox('id="test" name="test"')
|
|
204
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
205
|
+
|
|
206
|
+
// Set options via property
|
|
207
|
+
combobox.options = testOptions
|
|
208
|
+
await combobox.updateComplete
|
|
209
|
+
|
|
210
|
+
// Ensure listbox has also updated
|
|
211
|
+
const listbox = combobox.querySelector('pkt-listbox') as any
|
|
212
|
+
await listbox?.updateComplete
|
|
213
|
+
|
|
214
|
+
expect(combobox.options).toHaveLength(3)
|
|
215
|
+
expect(combobox.options[0].value).toBe('option1')
|
|
216
|
+
expect(combobox.options[0].label).toBe('Option 1')
|
|
217
|
+
expect(combobox.options[2].disabled).toBe(true)
|
|
218
|
+
|
|
219
|
+
expect(listbox?.options).toEqual(
|
|
220
|
+
expect.arrayContaining([expect.objectContaining({ value: 'option1', label: 'Option 1' })]),
|
|
221
|
+
)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test('handles options provided via slot', async () => {
|
|
225
|
+
const optionsMarkup = createOptions([
|
|
226
|
+
{ value: 'value1', label: 'Label 1' },
|
|
227
|
+
{ value: 'value2', label: 'Label 2' },
|
|
228
|
+
])
|
|
229
|
+
|
|
230
|
+
const container = await createCombobox('id="test" name="test"', optionsMarkup)
|
|
231
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
232
|
+
await combobox.updateComplete
|
|
233
|
+
|
|
234
|
+
// Options should be extracted from slot
|
|
235
|
+
expect(combobox.options.length).toBeGreaterThan(0)
|
|
236
|
+
expect(combobox.options.some((opt) => opt.value === 'value1')).toBe(true)
|
|
237
|
+
expect(combobox.options.some((opt) => opt.value === 'value2')).toBe(true)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('handles defaultOptions property', async () => {
|
|
241
|
+
const defaultOptions: IPktComboboxOption[] = [
|
|
242
|
+
{ value: 'default1', label: 'Default 1' },
|
|
243
|
+
{ value: 'default2', label: 'Default 2' },
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
const container = await createCombobox('id="test" name="test"')
|
|
247
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
248
|
+
|
|
249
|
+
combobox.defaultOptions = defaultOptions
|
|
250
|
+
await combobox.updateComplete
|
|
251
|
+
|
|
252
|
+
expect(combobox.options).toHaveLength(2)
|
|
253
|
+
expect(combobox.options[0].value).toBe('default1')
|
|
254
|
+
expect(combobox.options[1].value).toBe('default2')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('preserves userAdded options when defaultOptions change', async () => {
|
|
258
|
+
const container = await createCombobox('id="test" name="test" allowUserInput')
|
|
259
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
260
|
+
await combobox.updateComplete
|
|
261
|
+
|
|
262
|
+
// Add a user option
|
|
263
|
+
const userOption: IPktComboboxOption = {
|
|
264
|
+
value: 'user-added',
|
|
265
|
+
label: 'User Added',
|
|
266
|
+
userAdded: true,
|
|
267
|
+
}
|
|
268
|
+
combobox.options = [userOption]
|
|
269
|
+
await combobox.updateComplete
|
|
270
|
+
|
|
271
|
+
// Set default options
|
|
272
|
+
const defaultOptions: IPktComboboxOption[] = [{ value: 'default1', label: 'Default 1' }]
|
|
273
|
+
combobox.defaultOptions = defaultOptions
|
|
274
|
+
await combobox.updateComplete
|
|
275
|
+
|
|
276
|
+
// Should have both user-added and default options
|
|
277
|
+
expect(combobox.options).toHaveLength(2)
|
|
278
|
+
expect(combobox.options.some((opt) => opt.userAdded)).toBe(true)
|
|
279
|
+
expect(combobox.options.some((opt) => opt.value === 'default1')).toBe(true)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe('Value handling', () => {
|
|
284
|
+
test('handles single value correctly', async () => {
|
|
285
|
+
const container = await createCombobox('id="test" name="test" value="test-value"')
|
|
286
|
+
|
|
287
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
288
|
+
await combobox.updateComplete
|
|
289
|
+
|
|
290
|
+
expect(combobox.value).toBe('test-value')
|
|
291
|
+
|
|
292
|
+
// Internal _value should be an array with single item
|
|
293
|
+
expect(combobox['_value']).toEqual(['test-value'])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test('handles multiple values correctly', async () => {
|
|
297
|
+
const container = await createCombobox('id="test" name="test" value="value1,value2" multiple')
|
|
298
|
+
|
|
299
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
300
|
+
await combobox.updateComplete
|
|
301
|
+
|
|
302
|
+
expect(combobox.value).toEqual(['value1', 'value2'])
|
|
303
|
+
expect(combobox['_value']).toEqual(['value1', 'value2'])
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
test('handles array value correctly', async () => {
|
|
307
|
+
const container = await createCombobox('id="test" name="test" multiple')
|
|
308
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
309
|
+
|
|
310
|
+
combobox.value = ['array1', 'array2']
|
|
311
|
+
await combobox.updateComplete
|
|
312
|
+
|
|
313
|
+
expect(combobox['_value']).toEqual(['array1', 'array2'])
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
test('limits to single value when not multiple', async () => {
|
|
317
|
+
const container = await createCombobox('id="test" name="test"')
|
|
318
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
319
|
+
|
|
320
|
+
combobox.value = ['value1', 'value2']
|
|
321
|
+
await combobox.updateComplete
|
|
322
|
+
|
|
323
|
+
// Should only keep the first value when not multiple
|
|
324
|
+
expect(combobox['_value']).toEqual(['value1'])
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
describe('Placeholder functionality', () => {
|
|
329
|
+
test('shows placeholder when no value selected', async () => {
|
|
330
|
+
const container = await createCombobox('id="test" name="test" placeholder="Select an option"')
|
|
331
|
+
|
|
332
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
333
|
+
await combobox.updateComplete
|
|
334
|
+
|
|
335
|
+
expect(combobox.placeholder).toBe('Select an option')
|
|
336
|
+
|
|
337
|
+
const placeholder = combobox.querySelector('.pkt-combobox__placeholder')
|
|
338
|
+
expect(placeholder).toBeInTheDocument()
|
|
339
|
+
expect(placeholder?.textContent).toBe('Select an option')
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test('hides placeholder when value is selected', async () => {
|
|
343
|
+
const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }]
|
|
344
|
+
|
|
345
|
+
const container = await createCombobox(
|
|
346
|
+
'id="test" name="test" placeholder="Select an option" value="option1"',
|
|
347
|
+
)
|
|
348
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
349
|
+
combobox.options = testOptions
|
|
350
|
+
await combobox.updateComplete
|
|
351
|
+
|
|
352
|
+
const placeholder = combobox.querySelector('.pkt-combobox__placeholder')
|
|
353
|
+
expect(placeholder).not.toBeInTheDocument()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test('shows placeholder in multiple mode with outside tag placement', async () => {
|
|
357
|
+
const container = await createCombobox(
|
|
358
|
+
'id="test" name="test" placeholder="Select options" multiple tagPlacement="outside"',
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
362
|
+
await combobox.updateComplete
|
|
363
|
+
|
|
364
|
+
const placeholder = combobox.querySelector('.pkt-combobox__placeholder')
|
|
365
|
+
expect(placeholder).toBeInTheDocument()
|
|
366
|
+
expect(placeholder?.textContent).toBe('Select options')
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
describe('Tag placement functionality', () => {
|
|
371
|
+
test('renders tags inside input by default in multiple mode', async () => {
|
|
372
|
+
const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }]
|
|
373
|
+
|
|
374
|
+
const container = await createCombobox('id="test" name="test" multiple value="option1"')
|
|
375
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
376
|
+
combobox.options = testOptions
|
|
377
|
+
await combobox.updateComplete
|
|
378
|
+
|
|
379
|
+
const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside')
|
|
380
|
+
expect(outsideTags).not.toBeInTheDocument()
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test('renders tags outside input when tagPlacement is outside', async () => {
|
|
384
|
+
const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }]
|
|
385
|
+
|
|
386
|
+
const container = await createCombobox(
|
|
387
|
+
'id="test" name="test" multiple tagPlacement="outside" value="option1"',
|
|
388
|
+
)
|
|
389
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
390
|
+
combobox.options = testOptions
|
|
391
|
+
await combobox.updateComplete
|
|
392
|
+
|
|
393
|
+
const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside')
|
|
394
|
+
expect(outsideTags).toBeInTheDocument()
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('Input field functionality', () => {
|
|
399
|
+
test('renders hidden input when not allowUserInput or typeahead', async () => {
|
|
400
|
+
const container = await createCombobox('id="test" name="test-name"')
|
|
401
|
+
|
|
402
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
403
|
+
await combobox.updateComplete
|
|
404
|
+
|
|
405
|
+
const hiddenInput = combobox.querySelector('input[type="hidden"]')
|
|
406
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
407
|
+
|
|
408
|
+
expect(hiddenInput).toBeInTheDocument()
|
|
409
|
+
expect(textInput).not.toBeInTheDocument()
|
|
410
|
+
expect(hiddenInput?.getAttribute('id')).toBe('test-input')
|
|
411
|
+
expect(hiddenInput?.getAttribute('name')).toBe('test-name-input')
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
test('renders text input when allowUserInput is true', async () => {
|
|
415
|
+
const container = await createCombobox('id="test" name="test-name" allowUserInput')
|
|
416
|
+
|
|
417
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
418
|
+
await combobox.updateComplete
|
|
419
|
+
|
|
420
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
421
|
+
const hiddenInput = combobox.querySelector('input[type="hidden"]')
|
|
422
|
+
|
|
423
|
+
expect(textInput).toBeInTheDocument()
|
|
424
|
+
expect(hiddenInput).not.toBeInTheDocument()
|
|
425
|
+
expect(textInput?.getAttribute('id')).toBe('test-input')
|
|
426
|
+
expect(textInput?.getAttribute('name')).toBe('test-name-input')
|
|
427
|
+
expect(textInput?.getAttribute('role')).toBe('combobox')
|
|
428
|
+
expect(textInput?.getAttribute('aria-controls')).toBe('test-listbox')
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
test('renders text input when typeahead is true', async () => {
|
|
432
|
+
const container = await createCombobox('id="test" name="test" typeahead')
|
|
433
|
+
|
|
434
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
435
|
+
await combobox.updateComplete
|
|
436
|
+
|
|
437
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
438
|
+
expect(textInput).toBeInTheDocument()
|
|
439
|
+
expect(textInput?.getAttribute('aria-autocomplete')).toBe('both')
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
test('sets correct aria-autocomplete for allowUserInput', async () => {
|
|
443
|
+
const container = await createCombobox('id="test" name="test" allowUserInput')
|
|
444
|
+
|
|
445
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
446
|
+
await combobox.updateComplete
|
|
447
|
+
|
|
448
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
449
|
+
expect(textInput?.getAttribute('aria-autocomplete')).toBe('list')
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
describe('Dropdown functionality', () => {
|
|
454
|
+
test('opens dropdown when arrow button is clicked', async () => {
|
|
455
|
+
const container = await createCombobox('id="test" name="test"')
|
|
456
|
+
|
|
457
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
458
|
+
await combobox.updateComplete
|
|
459
|
+
|
|
460
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
461
|
+
const inputDiv = combobox.querySelector('.pkt-combobox__input')
|
|
462
|
+
|
|
463
|
+
expect(combobox['_isOptionsOpen']).toBe(false)
|
|
464
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
465
|
+
expect(inputDiv).not.toHaveClass('pkt-combobox__input--open')
|
|
466
|
+
|
|
467
|
+
// Click arrow to open
|
|
468
|
+
fireEvent.click(arrowButton!)
|
|
469
|
+
await combobox.updateComplete
|
|
470
|
+
|
|
471
|
+
expect(combobox['_isOptionsOpen']).toBe(true)
|
|
472
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
473
|
+
expect(inputDiv).toHaveClass('pkt-combobox__input--open')
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
test('toggles dropdown state with multiple clicks', async () => {
|
|
477
|
+
const container = await createCombobox('id="test" name="test"')
|
|
478
|
+
|
|
479
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
480
|
+
await combobox.updateComplete
|
|
481
|
+
|
|
482
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
483
|
+
|
|
484
|
+
// Click to open
|
|
485
|
+
fireEvent.click(arrowButton!)
|
|
486
|
+
await combobox.updateComplete
|
|
487
|
+
expect(combobox['_isOptionsOpen']).toBe(true)
|
|
488
|
+
|
|
489
|
+
// Click to close
|
|
490
|
+
fireEvent.click(arrowButton!)
|
|
491
|
+
await combobox.updateComplete
|
|
492
|
+
expect(combobox['_isOptionsOpen']).toBe(false)
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
test('does not open when disabled', async () => {
|
|
496
|
+
const container = await createCombobox('id="test" name="test" disabled')
|
|
497
|
+
|
|
498
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
499
|
+
await combobox.updateComplete
|
|
500
|
+
|
|
501
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
502
|
+
|
|
503
|
+
// Try to click when disabled
|
|
504
|
+
fireEvent.click(arrowButton!)
|
|
505
|
+
await combobox.updateComplete
|
|
506
|
+
|
|
507
|
+
expect(combobox['_isOptionsOpen']).toBe(false)
|
|
508
|
+
})
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
describe('Search functionality', () => {
|
|
512
|
+
test('handles search input when includeSearch is true', async () => {
|
|
513
|
+
const container = await createCombobox('id="test" name="test" includeSearch')
|
|
514
|
+
|
|
515
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
516
|
+
await combobox.updateComplete
|
|
517
|
+
|
|
518
|
+
const listbox = combobox.querySelector('pkt-listbox')
|
|
519
|
+
expect(listbox?.hasAttribute('includesearch')).toBe(true)
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
test('sets search placeholder correctly', async () => {
|
|
523
|
+
const container = await createCombobox(
|
|
524
|
+
'id="test" name="test" includeSearch searchPlaceholder="Search items..."',
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
528
|
+
await combobox.updateComplete
|
|
529
|
+
|
|
530
|
+
expect(combobox.searchPlaceholder).toBe('Search items...')
|
|
531
|
+
|
|
532
|
+
const listbox = combobox.querySelector('pkt-listbox') as any
|
|
533
|
+
expect(listbox?.searchPlaceholder).toBe('Search items...')
|
|
534
|
+
})
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
describe('Keyboard navigation', () => {
|
|
538
|
+
test('handles keyboard events on arrow button', async () => {
|
|
539
|
+
const container = await createCombobox('id="test" name="test"')
|
|
540
|
+
|
|
541
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
542
|
+
await combobox.updateComplete
|
|
543
|
+
|
|
544
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
545
|
+
|
|
546
|
+
// Test Enter key
|
|
547
|
+
fireEvent.keyDown(arrowButton!, { key: 'Enter' })
|
|
548
|
+
await combobox.updateComplete
|
|
549
|
+
|
|
550
|
+
expect(combobox['_isOptionsOpen']).toBe(true)
|
|
551
|
+
|
|
552
|
+
// Test Space key
|
|
553
|
+
fireEvent.keyDown(arrowButton!, { key: ' ' })
|
|
554
|
+
await combobox.updateComplete
|
|
555
|
+
|
|
556
|
+
expect(combobox['_isOptionsOpen']).toBe(false)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
test('handles input focus for text inputs', async () => {
|
|
560
|
+
const container = await createCombobox('id="test" name="test" allowUserInput')
|
|
561
|
+
|
|
562
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
563
|
+
await combobox.updateComplete
|
|
564
|
+
|
|
565
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
566
|
+
|
|
567
|
+
// Test focus event
|
|
568
|
+
fireEvent.focus(textInput!)
|
|
569
|
+
await combobox.updateComplete
|
|
570
|
+
|
|
571
|
+
// Test blur event
|
|
572
|
+
fireEvent.blur(textInput!)
|
|
573
|
+
await combobox.updateComplete
|
|
574
|
+
|
|
575
|
+
// Should not throw errors
|
|
576
|
+
expect(combobox).toBeInTheDocument()
|
|
577
|
+
})
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
describe('Max length functionality', () => {
|
|
581
|
+
test('handles maxlength property correctly', async () => {
|
|
582
|
+
const container = await createCombobox('id="test" name="test" multiple maxlength="3"')
|
|
583
|
+
|
|
584
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
585
|
+
await combobox.updateComplete
|
|
586
|
+
|
|
587
|
+
expect(combobox.maxlength).toBe(3)
|
|
588
|
+
|
|
589
|
+
const listbox = combobox.querySelector('pkt-listbox') as any
|
|
590
|
+
expect(listbox?.maxLength).toBe(3)
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
test('disables user input when max is reached', async () => {
|
|
594
|
+
const testOptions: IPktComboboxOption[] = [
|
|
595
|
+
{ value: 'option1', label: 'Option 1' },
|
|
596
|
+
{ value: 'option2', label: 'Option 2' },
|
|
597
|
+
{ value: 'option3', label: 'Option 3' },
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
const container = await createCombobox(
|
|
601
|
+
'id="test" name="test" multiple allowUserInput maxlength="2"',
|
|
602
|
+
)
|
|
603
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
604
|
+
combobox.options = testOptions
|
|
605
|
+
combobox.value = ['option1', 'option2'] // Set to max
|
|
606
|
+
await combobox.updateComplete
|
|
607
|
+
|
|
608
|
+
const listbox = combobox.querySelector('pkt-listbox') as any
|
|
609
|
+
await listbox?.updateComplete
|
|
610
|
+
|
|
611
|
+
expect(listbox?.maxIsReached).toBe(true)
|
|
612
|
+
})
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
describe('Error handling', () => {
|
|
616
|
+
test('applies error styling when hasError is true', async () => {
|
|
617
|
+
const container = await createCombobox('id="test" name="test"')
|
|
618
|
+
|
|
619
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
620
|
+
combobox.hasError = true
|
|
621
|
+
await combobox.updateComplete
|
|
622
|
+
|
|
623
|
+
const inputDiv = combobox.querySelector('.pkt-combobox__input')
|
|
624
|
+
expect(inputDiv).toHaveClass('pkt-combobox__input--error')
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
test('passes error state to input wrapper', async () => {
|
|
628
|
+
const container = await createCombobox('id="test" name="test" error-message="Test error"')
|
|
629
|
+
|
|
630
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
631
|
+
combobox.hasError = true
|
|
632
|
+
await combobox.updateComplete
|
|
633
|
+
|
|
634
|
+
const wrapper = combobox.querySelector('pkt-input-wrapper')
|
|
635
|
+
expect(wrapper?.hasAttribute('haserror')).toBe(true)
|
|
636
|
+
})
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
describe('Accessibility', () => {
|
|
640
|
+
test('has no accessibility violations', async () => {
|
|
641
|
+
const container = await createCombobox(
|
|
642
|
+
'id="accessible-combobox" name="test" label="Accessible Combobox"',
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
646
|
+
await combobox.updateComplete
|
|
647
|
+
|
|
648
|
+
const results = await axe(combobox)
|
|
649
|
+
expect(results).toHaveNoViolations()
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
test('sets correct ARIA attributes on arrow button', async () => {
|
|
653
|
+
const container = await createCombobox('id="test-aria" name="test" label="Test"')
|
|
654
|
+
|
|
655
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
656
|
+
await combobox.updateComplete
|
|
657
|
+
|
|
658
|
+
const arrowButton = combobox.querySelector('.pkt-combobox__arrow')
|
|
659
|
+
expect(arrowButton?.getAttribute('aria-controls')).toBe('test-aria-listbox')
|
|
660
|
+
expect(arrowButton?.getAttribute('aria-haspopup')).toBe('listbox')
|
|
661
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
662
|
+
expect(arrowButton?.getAttribute('aria-label')).toBe('Åpne liste')
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
test('sets correct ARIA attributes on text input', async () => {
|
|
666
|
+
const container = await createCombobox(
|
|
667
|
+
'id="test-input" name="test" label="Test Label" allowUserInput',
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
671
|
+
await combobox.updateComplete
|
|
672
|
+
|
|
673
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
674
|
+
expect(textInput?.getAttribute('role')).toBe('combobox')
|
|
675
|
+
expect(textInput?.getAttribute('aria-controls')).toBe('test-input-listbox')
|
|
676
|
+
expect(textInput?.getAttribute('aria-label')).toBe('Test Label')
|
|
677
|
+
expect(textInput?.getAttribute('aria-autocomplete')).toBe('list')
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
test('sets correct ARIA attributes for multiple selection', async () => {
|
|
681
|
+
const container = await createCombobox('id="test" name="test" allowUserInput multiple')
|
|
682
|
+
|
|
683
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
684
|
+
await combobox.updateComplete
|
|
685
|
+
|
|
686
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
687
|
+
expect(textInput?.getAttribute('aria-multiselectable')).toBe('true')
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
test('sets correct aria-activedescendant when value is selected', async () => {
|
|
691
|
+
const testOptions: IPktComboboxOption[] = [{ value: 'option1', label: 'Option 1' }]
|
|
692
|
+
|
|
693
|
+
const container = await createCombobox('id="test" name="test" allowUserInput value="option1"')
|
|
694
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
695
|
+
combobox.options = testOptions
|
|
696
|
+
await combobox.updateComplete
|
|
697
|
+
|
|
698
|
+
const textInput = combobox.querySelector('input[type="text"]')
|
|
699
|
+
expect(textInput?.getAttribute('aria-activedescendant')).toBe('test-listbox--1')
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
describe('Event handling', () => {
|
|
704
|
+
test('dispatches search event when search value changes', async () => {
|
|
705
|
+
const container = await createCombobox('id="test" name="test" includeSearch')
|
|
706
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
707
|
+
await combobox.updateComplete
|
|
708
|
+
|
|
709
|
+
const searchEventSpy = jest.fn()
|
|
710
|
+
combobox.addEventListener('search', searchEventSpy)
|
|
711
|
+
|
|
712
|
+
// Simulate search change
|
|
713
|
+
combobox['_search'] = 'test search'
|
|
714
|
+
await combobox.updateComplete
|
|
715
|
+
|
|
716
|
+
expect(searchEventSpy).toHaveBeenCalledWith(
|
|
717
|
+
expect.objectContaining({
|
|
718
|
+
detail: 'test search',
|
|
719
|
+
}),
|
|
720
|
+
)
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
test('handles listbox events correctly', async () => {
|
|
724
|
+
const container = await createCombobox('id="test" name="test"')
|
|
725
|
+
const combobox = container.querySelector('pkt-combobox') as PktCombobox
|
|
726
|
+
await combobox.updateComplete
|
|
727
|
+
|
|
728
|
+
const listbox = combobox.querySelector('pkt-listbox')
|
|
729
|
+
|
|
730
|
+
// Test close-options event
|
|
731
|
+
fireEvent(listbox!, new CustomEvent('close-options'))
|
|
732
|
+
await combobox.updateComplete
|
|
733
|
+
|
|
734
|
+
expect(combobox['_isOptionsOpen']).toBe(false)
|
|
735
|
+
})
|
|
736
|
+
})
|
|
737
|
+
})
|