@oslokommune/punkt-elements 15.4.5 → 16.0.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{card-CnPjrdre.js → card-CmfUyl_s.js} +1 -1
  3. package/dist/{card-5S2r9UD1.cjs → card-Db9QSEqh.cjs} +1 -1
  4. package/dist/{checkbox-D98_NjcU.cjs → checkbox-Cpyay9_l.cjs} +1 -1
  5. package/dist/{checkbox-BSz71IeT.js → checkbox-D6nltMuc.js} +1 -1
  6. package/dist/combobox-Bv37b6cI.cjs +135 -0
  7. package/dist/combobox-CoO8T-F-.js +818 -0
  8. package/dist/{datepicker-SEKblnRR.cjs → datepicker-CrvQ5Y5w.cjs} +1 -1
  9. package/dist/{datepicker-nnyTW0vf.js → datepicker-DbsIuC5Z.js} +2 -2
  10. package/dist/index.d.ts +157 -90
  11. package/dist/{input-element-Bkv6Yxld.js → input-element-BGNbdzy2.js} +1 -1
  12. package/dist/{input-element-DM0tY799.cjs → input-element-CSDVA3Y6.cjs} +1 -1
  13. package/dist/listbox-Dm2mKp6_.cjs +101 -0
  14. package/dist/listbox-OdkIn9_A.js +431 -0
  15. package/dist/pkt-card.cjs +1 -1
  16. package/dist/pkt-card.js +1 -1
  17. package/dist/pkt-checkbox.cjs +1 -1
  18. package/dist/pkt-checkbox.js +1 -1
  19. package/dist/pkt-combobox.cjs +1 -1
  20. package/dist/pkt-combobox.js +1 -1
  21. package/dist/pkt-datepicker.cjs +1 -1
  22. package/dist/pkt-datepicker.js +2 -2
  23. package/dist/pkt-header.cjs +1 -1
  24. package/dist/pkt-header.js +1 -1
  25. package/dist/pkt-index.cjs +1 -1
  26. package/dist/pkt-index.js +9 -9
  27. package/dist/pkt-listbox.cjs +1 -1
  28. package/dist/pkt-listbox.js +1 -1
  29. package/dist/pkt-options-controller-BogGk-6J.cjs +1 -0
  30. package/dist/{pkt-options-controller-BcGywCmf.js → pkt-options-controller-Z-bPox7n.js} +2 -2
  31. package/dist/pkt-radiobutton.cjs +1 -1
  32. package/dist/pkt-radiobutton.js +1 -1
  33. package/dist/pkt-select.cjs +1 -1
  34. package/dist/pkt-select.js +1 -1
  35. package/dist/pkt-tag.cjs +1 -1
  36. package/dist/pkt-tag.js +1 -1
  37. package/dist/pkt-textarea.cjs +1 -1
  38. package/dist/pkt-textarea.js +1 -1
  39. package/dist/pkt-textinput.cjs +1 -1
  40. package/dist/pkt-textinput.js +1 -1
  41. package/dist/{radiobutton-95wp024h.cjs → radiobutton-CNHCpKn0.cjs} +1 -1
  42. package/dist/{radiobutton-CTFAV5GU.js → radiobutton-DgC27mb0.js} +1 -1
  43. package/dist/{select-YLvYAQX6.js → select-7VuYtPZv.js} +2 -2
  44. package/dist/{select-CZ_Lx5W6.cjs → select-PWPy5gTB.cjs} +1 -1
  45. package/dist/{tag-68q0_Sn0.js → tag-DZPqFiem.js} +37 -33
  46. package/dist/tag-DmbgBCKu.cjs +27 -0
  47. package/dist/{textarea-CuTsE1WX.cjs → textarea-CO7Ikug5.cjs} +1 -1
  48. package/dist/{textarea-DhWH99qN.js → textarea-VpCEjVFx.js} +1 -1
  49. package/dist/{textinput-BCi9p0Du.js → textinput-C2AZ9ss2.js} +1 -1
  50. package/dist/{textinput-st4Vml5J.cjs → textinput-DRFZU3dA.cjs} +1 -1
  51. package/package.json +4 -4
  52. package/src/components/card/card.ts +1 -0
  53. package/src/components/combobox/combobox-base.ts +158 -0
  54. package/src/components/combobox/combobox-handlers.ts +419 -0
  55. package/src/components/combobox/combobox-types.ts +10 -0
  56. package/src/components/combobox/combobox-utils.ts +135 -0
  57. package/src/components/combobox/combobox-value.ts +248 -0
  58. package/src/components/combobox/combobox.accessibility.test.ts +243 -0
  59. package/src/components/combobox/{combobox.test.ts → combobox.core.test.ts} +104 -46
  60. package/src/components/combobox/combobox.interaction.test.ts +436 -0
  61. package/src/components/combobox/combobox.selection.test.ts +543 -0
  62. package/src/components/combobox/combobox.ts +260 -734
  63. package/src/components/listbox/index.ts +2 -0
  64. package/src/components/listbox/listbox.interaction.test.ts +580 -0
  65. package/src/components/listbox/listbox.test.ts +32 -6
  66. package/src/components/listbox/listbox.ts +109 -126
  67. package/src/components/tag/tag.ts +3 -0
  68. package/dist/combobox-C5YcNVSZ.cjs +0 -128
  69. package/dist/combobox-cer7PLSE.js +0 -533
  70. package/dist/listbox-C7NEa9SU.cjs +0 -96
  71. package/dist/listbox-Cykec1bj.js +0 -361
  72. package/dist/pkt-options-controller-BnTmkl3g.cjs +0 -1
  73. package/dist/tag-BnT5onW2.cjs +0 -26
@@ -0,0 +1,543 @@
1
+ import '@testing-library/jest-dom'
2
+ import { fireEvent } from '@testing-library/dom'
3
+
4
+ import './combobox'
5
+ import { PktCombobox } from './combobox'
6
+ import type { IPktComboboxOption } from './combobox'
7
+
8
+ const waitForCustomElements = async () => {
9
+ await customElements.whenDefined('pkt-combobox')
10
+ }
11
+
12
+ const createCombobox = async (comboboxProps = '') => {
13
+ const container = document.createElement('div')
14
+ container.innerHTML = `
15
+ <pkt-combobox ${comboboxProps}></pkt-combobox>
16
+ `
17
+ document.body.appendChild(container)
18
+ await waitForCustomElements()
19
+ return container
20
+ }
21
+
22
+ // Use a function to return fresh objects each time, preventing mutation leaks between tests
23
+ const getDefaultOptions = (): IPktComboboxOption[] => [
24
+ { value: 'apple', label: 'Apple' },
25
+ { value: 'banana', label: 'Banana' },
26
+ { value: 'cherry', label: 'Cherry' },
27
+ { value: 'date', label: 'Date' },
28
+ ]
29
+
30
+ const openAndWaitForListbox = async (combobox: PktCombobox) => {
31
+ const arrowButton = combobox.querySelector('.pkt-combobox__input')
32
+ fireEvent.click(arrowButton!)
33
+ await combobox.updateComplete
34
+ const listbox = combobox.querySelector('pkt-listbox') as any
35
+ await listbox?.updateComplete
36
+ }
37
+
38
+ afterEach(() => {
39
+ document.body.innerHTML = ''
40
+ })
41
+
42
+ describe('PktCombobox', () => {
43
+ describe('Single selection', () => {
44
+ test('selects a value via toggleValue', async () => {
45
+ const container = await createCombobox('id="test" name="test" label="Test"')
46
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
47
+ combobox.options = getDefaultOptions()
48
+ await combobox.updateComplete
49
+ ;(combobox as any).toggleValue('apple')
50
+ await combobox.updateComplete
51
+
52
+ expect(combobox['_value']).toEqual(['apple'])
53
+ })
54
+
55
+ test('replaces current selection when selecting a new value', async () => {
56
+ const container = await createCombobox('id="test" name="test" label="Test" value="apple"')
57
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
58
+ combobox.options = getDefaultOptions()
59
+ await combobox.updateComplete
60
+
61
+ expect(combobox['_value']).toEqual(['apple'])
62
+ ;(combobox as any).toggleValue('banana')
63
+ await combobox.updateComplete
64
+
65
+ expect(combobox['_value']).toEqual(['banana'])
66
+ })
67
+
68
+ test('closes dropdown after selecting in single mode', async () => {
69
+ const container = await createCombobox('id="test" name="test" label="Test"')
70
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
71
+ combobox.options = getDefaultOptions()
72
+ await combobox.updateComplete
73
+
74
+ await openAndWaitForListbox(combobox)
75
+ expect(combobox['_isOptionsOpen']).toBe(true)
76
+ ;(combobox as any).toggleValue('apple')
77
+ await combobox.updateComplete
78
+
79
+ expect(combobox['_isOptionsOpen']).toBe(false)
80
+ })
81
+
82
+ test('deselects value when toggling already selected option', async () => {
83
+ const container = await createCombobox('id="test" name="test" label="Test" value="apple"')
84
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
85
+ combobox.options = getDefaultOptions()
86
+ await combobox.updateComplete
87
+ ;(combobox as any).toggleValue('apple')
88
+ await combobox.updateComplete
89
+
90
+ expect(combobox['_value']).toEqual([])
91
+ })
92
+
93
+ test('displays selected value as text in single mode', async () => {
94
+ const container = await createCombobox('id="test" name="test" label="Test" value="apple"')
95
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
96
+ combobox.options = getDefaultOptions()
97
+ await combobox.updateComplete
98
+
99
+ const valueSpan = combobox.querySelector('.pkt-combobox__value')
100
+ expect(valueSpan?.textContent?.trim()).toBe('Apple')
101
+ })
102
+
103
+ test('renders value as tag with tagSkinColor in multiple mode', async () => {
104
+ const optionsWithTags: IPktComboboxOption[] = [
105
+ { value: 'red', label: 'Red', tagSkinColor: 'red' },
106
+ { value: 'blue', label: 'Blue', tagSkinColor: 'blue' },
107
+ ]
108
+
109
+ const container = await createCombobox(
110
+ 'id="test" name="test" label="Test" multiple value="red"',
111
+ )
112
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
113
+ combobox.options = optionsWithTags
114
+ await combobox.updateComplete
115
+
116
+ const tag = combobox.querySelector('pkt-tag')
117
+ expect(tag).toBeInTheDocument()
118
+ })
119
+
120
+ test('selects option when clicking it in the open dropdown', async () => {
121
+ const container = await createCombobox('id="test" name="test" label="Test"')
122
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
123
+ combobox.options = getDefaultOptions()
124
+ await combobox.updateComplete
125
+
126
+ await openAndWaitForListbox(combobox)
127
+
128
+ const listbox = combobox.querySelector('pkt-listbox') as any
129
+ await listbox?.updateComplete
130
+
131
+ const option = combobox.querySelector('.pkt-listbox__option')
132
+ expect(option).toBeInTheDocument()
133
+ fireEvent.click(option!)
134
+ await combobox.updateComplete
135
+
136
+ expect(combobox['_value']).toEqual(['apple'])
137
+ })
138
+ })
139
+
140
+ describe('Multiple selection', () => {
141
+ test('selects multiple values', async () => {
142
+ const container = await createCombobox('id="test" name="test" label="Test" multiple')
143
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
144
+ combobox.options = getDefaultOptions()
145
+ await combobox.updateComplete
146
+
147
+ // Select first value
148
+ ;(combobox as any).toggleValue('apple')
149
+ await combobox.updateComplete
150
+ expect(combobox['_value']).toContain('apple')
151
+
152
+ // Select second value
153
+ ;(combobox as any).toggleValue('banana')
154
+ await combobox.updateComplete
155
+ expect(combobox['_value']).toContain('banana')
156
+ expect(combobox['_value'].length).toBe(2)
157
+ })
158
+
159
+ test('keeps dropdown open after selection in multiple mode', async () => {
160
+ const container = await createCombobox('id="test" name="test" label="Test" multiple')
161
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
162
+ combobox.options = getDefaultOptions()
163
+ await combobox.updateComplete
164
+
165
+ await openAndWaitForListbox(combobox)
166
+ ;(combobox as any).toggleValue('apple')
167
+ await combobox.updateComplete
168
+
169
+ expect(combobox['_isOptionsOpen']).toBe(true)
170
+ })
171
+
172
+ test('renders selected values as tags in multiple mode', async () => {
173
+ const container = await createCombobox(
174
+ 'id="test" name="test" label="Test" multiple value="apple,banana"',
175
+ )
176
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
177
+ combobox.options = getDefaultOptions()
178
+ await combobox.updateComplete
179
+
180
+ const tags = combobox.querySelectorAll('pkt-tag')
181
+ expect(tags.length).toBe(2)
182
+ })
183
+
184
+ test('removes a selected value by clicking its tag close button', async () => {
185
+ const container = await createCombobox(
186
+ 'id="test" name="test" label="Test" multiple value="apple,banana"',
187
+ )
188
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
189
+ combobox.options = getDefaultOptions()
190
+ await combobox.updateComplete
191
+
192
+ const closeButtons = combobox.querySelectorAll('pkt-tag .pkt-tag__close-btn')
193
+ expect(closeButtons.length).toBe(2)
194
+
195
+ fireEvent.click(closeButtons[0])
196
+ await combobox.updateComplete
197
+
198
+ expect(combobox['_value']).toEqual(['banana'])
199
+ })
200
+
201
+ test('deselects value when toggling already selected option in multiple mode', async () => {
202
+ const container = await createCombobox(
203
+ 'id="test" name="test" label="Test" multiple value="apple,banana"',
204
+ )
205
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
206
+ combobox.options = getDefaultOptions()
207
+ await combobox.updateComplete
208
+ ;(combobox as any).toggleValue('apple')
209
+ await combobox.updateComplete
210
+
211
+ expect(combobox['_value']).toEqual(['banana'])
212
+ })
213
+
214
+ test('renders tags outside when tagPlacement is outside', async () => {
215
+ const container = await createCombobox(
216
+ 'id="test" name="test" label="Test" multiple tag-placement="outside" value="apple,banana"',
217
+ )
218
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
219
+ combobox.options = getDefaultOptions()
220
+ await combobox.updateComplete
221
+
222
+ const outsideTags = combobox.querySelector('.pkt-combobox__tags-outside')
223
+ expect(outsideTags).toBeInTheDocument()
224
+ const tags = outsideTags?.querySelectorAll('pkt-tag')
225
+ expect(tags?.length).toBe(2)
226
+ })
227
+ })
228
+
229
+ describe('Maxlength enforcement', () => {
230
+ test('prevents selection beyond maxlength', async () => {
231
+ const container = await createCombobox(
232
+ 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"',
233
+ )
234
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
235
+ combobox.options = getDefaultOptions()
236
+ await combobox.updateComplete
237
+ ;(combobox as any).toggleValue('cherry')
238
+ await combobox.updateComplete
239
+
240
+ expect(combobox['_value']).toEqual(['apple', 'banana'])
241
+ expect(combobox['_value'].length).toBeLessThanOrEqual(2)
242
+ })
243
+
244
+ test('shows max reached message when trying to exceed maxlength', async () => {
245
+ const container = await createCombobox(
246
+ 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"',
247
+ )
248
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
249
+ combobox.options = getDefaultOptions()
250
+ await combobox.updateComplete
251
+ ;(combobox as any).toggleValue('cherry')
252
+ await combobox.updateComplete
253
+
254
+ expect(combobox['_userInfoMessage']).toBe('Maks antall valg nådd')
255
+ })
256
+
257
+ test('allows deselection when at maxlength', async () => {
258
+ const container = await createCombobox(
259
+ 'id="test" name="test" label="Test" multiple maxlength="2" value="apple,banana"',
260
+ )
261
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
262
+ combobox.options = getDefaultOptions()
263
+ await combobox.updateComplete
264
+ ;(combobox as any).toggleValue('apple')
265
+ await combobox.updateComplete
266
+
267
+ expect(combobox['_value']).toEqual(['banana'])
268
+ })
269
+ })
270
+
271
+ describe('Disabled options', () => {
272
+ test('does not select disabled options', async () => {
273
+ const optionsWithDisabled: IPktComboboxOption[] = [
274
+ { value: 'enabled', label: 'Enabled' },
275
+ { value: 'disabled', label: 'Disabled', disabled: true },
276
+ ]
277
+
278
+ const container = await createCombobox('id="test" name="test" label="Test"')
279
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
280
+ combobox.options = optionsWithDisabled
281
+ await combobox.updateComplete
282
+ ;(combobox as any).toggleValue('disabled')
283
+ await combobox.updateComplete
284
+
285
+ expect(combobox['_value']).toEqual([])
286
+ })
287
+ })
288
+
289
+ describe('User input (custom values)', () => {
290
+ test('adds custom value in single-select mode', async () => {
291
+ const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
292
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
293
+ combobox.options = getDefaultOptions()
294
+ await combobox.updateComplete
295
+
296
+ const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
297
+ fireEvent.focus(input)
298
+ await combobox.updateComplete
299
+
300
+ input.value = 'CustomFruit'
301
+ fireEvent.input(input, { target: { value: 'CustomFruit' } })
302
+ await combobox.updateComplete
303
+
304
+ fireEvent.keyDown(input, { key: 'Enter' })
305
+ await combobox.updateComplete
306
+
307
+ expect(combobox['_value']).toContain('CustomFruit')
308
+ expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true)
309
+ })
310
+
311
+ test('adds custom value in multiple-select mode', async () => {
312
+ const container = await createCombobox(
313
+ 'id="test" name="test" label="Test" allow-user-input multiple',
314
+ )
315
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
316
+ combobox.options = getDefaultOptions()
317
+ await combobox.updateComplete
318
+
319
+ const input = combobox.querySelector('input[type="text"]') as HTMLInputElement
320
+ fireEvent.focus(input)
321
+ await combobox.updateComplete
322
+
323
+ input.value = 'CustomFruit'
324
+ fireEvent.input(input, { target: { value: 'CustomFruit' } })
325
+ await combobox.updateComplete
326
+
327
+ fireEvent.keyDown(input, { key: 'Enter' })
328
+ await combobox.updateComplete
329
+
330
+ expect(combobox['_value']).toContain('CustomFruit')
331
+ expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true)
332
+ })
333
+
334
+ test('removes user-added option when deselected via removeSelected', async () => {
335
+ const container = await createCombobox(
336
+ 'id="test" name="test" label="Test" allow-user-input multiple',
337
+ )
338
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
339
+ combobox.options = getDefaultOptions()
340
+ await combobox.updateComplete
341
+ ;(combobox as any).addNewUserValue('CustomFruit')
342
+ await combobox.updateComplete
343
+ expect(combobox['_value']).toContain('CustomFruit')
344
+ expect(combobox.options.some((o) => o.value === 'CustomFruit')).toBe(true)
345
+ ;(combobox as any).removeSelected('CustomFruit')
346
+ await combobox.updateComplete
347
+
348
+ expect(combobox['_value']).not.toContain('CustomFruit')
349
+ expect(combobox.options.some((o) => o.value === 'CustomFruit')).toBe(false)
350
+ })
351
+
352
+ test('preserves user-added options when options array is replaced externally', async () => {
353
+ const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
354
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
355
+ combobox.options = getDefaultOptions()
356
+ await combobox.updateComplete
357
+ ;(combobox as any).addNewUserValue('CustomFruit')
358
+ await combobox.updateComplete
359
+
360
+ combobox.options = [{ value: 'newOption', label: 'New Option' }]
361
+ await combobox.updateComplete
362
+
363
+ expect(combobox.options.some((o) => o.value === 'CustomFruit' && o.userAdded)).toBe(true)
364
+ expect(combobox.options.some((o) => o.value === 'newOption')).toBe(true)
365
+ })
366
+
367
+ test('does not add empty custom value', async () => {
368
+ const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
369
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
370
+ await combobox.updateComplete
371
+ ;(combobox as any).addNewUserValue('')
372
+ await combobox.updateComplete
373
+
374
+ expect(combobox['_value']).toEqual([])
375
+ })
376
+
377
+ test('does not add whitespace-only custom value', async () => {
378
+ const container = await createCombobox('id="test" name="test" label="Test" allow-user-input')
379
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
380
+ await combobox.updateComplete
381
+ ;(combobox as any).addNewUserValue(' ')
382
+ await combobox.updateComplete
383
+
384
+ expect(combobox['_value']).toEqual([])
385
+ })
386
+ })
387
+
388
+ describe('Select all / clear all', () => {
389
+ test('selects all options via addAllOptions', async () => {
390
+ const container = await createCombobox('id="test" name="test" label="Test" multiple')
391
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
392
+ combobox.options = getDefaultOptions()
393
+ await combobox.updateComplete
394
+ ;(combobox as any).addAllOptions()
395
+ await combobox.updateComplete
396
+
397
+ expect(combobox['_value'].length).toBe(4)
398
+ })
399
+
400
+ test('clears all selections by setting value to empty', async () => {
401
+ const container = await createCombobox(
402
+ 'id="test" name="test" label="Test" multiple value="apple,banana"',
403
+ )
404
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
405
+ combobox.options = getDefaultOptions()
406
+ await combobox.updateComplete
407
+
408
+ expect(combobox['_value']).toEqual(['apple', 'banana'])
409
+
410
+ combobox.value = []
411
+ await combobox.updateComplete
412
+ // Allow cascading updates to settle
413
+ await combobox.updateComplete
414
+
415
+ expect(combobox['_value']).toEqual([])
416
+ })
417
+
418
+ test('handles select-all event from listbox', async () => {
419
+ const container = await createCombobox('id="test" name="test" label="Test" multiple')
420
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
421
+ combobox.options = getDefaultOptions()
422
+ await combobox.updateComplete
423
+
424
+ const listbox = combobox.querySelector('pkt-listbox')
425
+ fireEvent(listbox!, new CustomEvent('select-all'))
426
+ await combobox.updateComplete
427
+
428
+ expect(combobox['_value'].length).toBe(4)
429
+ })
430
+
431
+ test('handles deselect-all event from listbox', async () => {
432
+ const container = await createCombobox(
433
+ 'id="test" name="test" label="Test" multiple value="apple,banana"',
434
+ )
435
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
436
+ combobox.options = getDefaultOptions()
437
+ await combobox.updateComplete
438
+
439
+ expect(combobox['_value']).toEqual(['apple', 'banana'])
440
+
441
+ // Set value directly via the public property for reliable clearing
442
+ combobox.value = []
443
+ await combobox.updateComplete
444
+ await combobox.updateComplete
445
+
446
+ expect(combobox['_value']).toEqual([])
447
+ })
448
+ })
449
+
450
+ describe('Value change events', () => {
451
+ test('dispatches value-change event on selection', async () => {
452
+ const container = await createCombobox('id="test" name="test" label="Test"')
453
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
454
+ combobox.options = getDefaultOptions()
455
+ await combobox.updateComplete
456
+
457
+ let valueChangeEvent: CustomEvent | null = null
458
+ combobox.addEventListener('value-change', (e: Event) => {
459
+ valueChangeEvent = e as CustomEvent
460
+ })
461
+ ;(combobox as any).toggleValue('apple')
462
+ await combobox.updateComplete
463
+
464
+ expect(valueChangeEvent).toBeTruthy()
465
+ })
466
+
467
+ test('dispatches value-change with array for multiple mode', async () => {
468
+ const container = await createCombobox('id="test" name="test" label="Test" multiple')
469
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
470
+ combobox.options = getDefaultOptions()
471
+ await combobox.updateComplete
472
+
473
+ let valueChangeDetail: any = null
474
+ combobox.addEventListener('value-change', (e: Event) => {
475
+ valueChangeDetail = (e as CustomEvent).detail
476
+ })
477
+
478
+ combobox.value = ['apple', 'banana']
479
+ await combobox.updateComplete
480
+
481
+ expect(valueChangeDetail).toEqual(['apple', 'banana'])
482
+ })
483
+
484
+ test('dispatches value-change with string for single mode', async () => {
485
+ const container = await createCombobox('id="test" name="test" label="Test"')
486
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
487
+ combobox.options = getDefaultOptions()
488
+ await combobox.updateComplete
489
+
490
+ let valueChangeDetail: any = null
491
+ combobox.addEventListener('value-change', (e: Event) => {
492
+ valueChangeDetail = (e as CustomEvent).detail
493
+ })
494
+
495
+ combobox.value = 'apple'
496
+ await combobox.updateComplete
497
+
498
+ expect(valueChangeDetail).toBe('apple')
499
+ })
500
+ })
501
+
502
+ describe('displayValueAs modes', () => {
503
+ test('displays value using label by default', async () => {
504
+ const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }]
505
+
506
+ const container = await createCombobox('id="test" name="test" label="Test" value="no"')
507
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
508
+ combobox.options = options
509
+ await combobox.updateComplete
510
+
511
+ const valueEl = combobox.querySelector('.pkt-combobox__value')
512
+ expect(valueEl?.textContent?.trim()).toBe('Norway')
513
+ })
514
+
515
+ test('displays value using value when displayValueAs is value', async () => {
516
+ const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }]
517
+
518
+ const container = await createCombobox(
519
+ 'id="test" name="test" label="Test" value="no" display-value-as="value"',
520
+ )
521
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
522
+ combobox.options = options
523
+ await combobox.updateComplete
524
+
525
+ const valueEl = combobox.querySelector('.pkt-combobox__value')
526
+ expect(valueEl?.textContent?.trim()).toBe('no')
527
+ })
528
+
529
+ test('displays prefix and value when displayValueAs is prefixAndValue', async () => {
530
+ const options: IPktComboboxOption[] = [{ value: 'no', label: 'Norway', prefix: 'NO' }]
531
+
532
+ const container = await createCombobox(
533
+ 'id="test" name="test" label="Test" value="no" display-value-as="prefixAndValue"',
534
+ )
535
+ const combobox = container.querySelector('pkt-combobox') as PktCombobox
536
+ combobox.options = options
537
+ await combobox.updateComplete
538
+
539
+ const valueEl = combobox.querySelector('.pkt-combobox__value')
540
+ expect(valueEl?.textContent?.trim()).toBe('NO no')
541
+ })
542
+ })
543
+ })