@oslokommune/punkt-react 15.4.6 → 16.0.0
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 +74 -0
- package/dist/index.d.ts +38 -15
- package/dist/punkt-react.es.js +12025 -10664
- package/dist/punkt-react.umd.js +562 -549
- package/package.json +5 -5
- package/src/components/accordion/Accordion.test.tsx +3 -2
- package/src/components/alert/Alert.test.tsx +2 -1
- package/src/components/backlink/BackLink.test.tsx +2 -1
- package/src/components/button/Button.test.tsx +4 -3
- package/src/components/calendar/Calendar.interaction.test.tsx +2 -1
- package/src/components/checkbox/Checkbox.test.tsx +2 -1
- package/src/components/combobox/Combobox.accessibility.test.tsx +277 -0
- package/src/components/combobox/Combobox.core.test.tsx +469 -0
- package/src/components/combobox/Combobox.interaction.test.tsx +607 -0
- package/src/components/combobox/Combobox.selection.test.tsx +548 -0
- package/src/components/combobox/Combobox.tsx +59 -54
- package/src/components/combobox/ComboboxInput.tsx +140 -0
- package/src/components/combobox/ComboboxTags.tsx +110 -0
- package/src/components/combobox/Listbox.tsx +172 -0
- package/src/components/combobox/types.ts +145 -0
- package/src/components/combobox/useComboboxState.ts +1141 -0
- package/src/components/datepicker/Datepicker.accessibility.test.tsx +5 -4
- package/src/components/datepicker/Datepicker.input.test.tsx +3 -2
- package/src/components/datepicker/Datepicker.selection.test.tsx +8 -8
- package/src/components/datepicker/Datepicker.validation.test.tsx +2 -1
- package/src/components/radio/RadioButton.test.tsx +3 -2
- package/src/components/searchinput/SearchInput.test.tsx +6 -5
- package/src/components/tabs/Tabs.test.tsx +13 -12
- package/src/components/tag/Tag.test.tsx +2 -1
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
|
|
3
|
+
import { act, fireEvent, render } from '@testing-library/react'
|
|
4
|
+
import { vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import type { IPktComboboxOption } from 'shared-types/combobox'
|
|
7
|
+
import { PktCombobox } from './Combobox'
|
|
8
|
+
import type { IPktCombobox } from './types'
|
|
9
|
+
|
|
10
|
+
const comboboxId = 'test-combobox'
|
|
11
|
+
const label = 'Test Combobox'
|
|
12
|
+
|
|
13
|
+
const getDefaultOptions = (): IPktComboboxOption[] => [
|
|
14
|
+
{ value: 'apple', label: 'Apple' },
|
|
15
|
+
{ value: 'banana', label: 'Banana' },
|
|
16
|
+
{ value: 'cherry', label: 'Cherry' },
|
|
17
|
+
{ value: 'date', label: 'Date' },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
const createComboboxTest = (props: Partial<IPktCombobox> = {}) => {
|
|
21
|
+
const defaultProps: IPktCombobox = {
|
|
22
|
+
label,
|
|
23
|
+
id: comboboxId,
|
|
24
|
+
...props,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return render(<PktCombobox {...defaultProps} />)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getFormInputValue = (container: HTMLElement) => {
|
|
31
|
+
return (container.querySelector('input.pkt-visually-hidden') as HTMLInputElement)?.value ?? ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('PktCombobox', () => {
|
|
35
|
+
describe('Keyboard navigation', () => {
|
|
36
|
+
test('opens dropdown with Enter on arrow button', () => {
|
|
37
|
+
const { container } = createComboboxTest()
|
|
38
|
+
|
|
39
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
40
|
+
fireEvent.keyDown(arrowButton!, { key: 'Enter' })
|
|
41
|
+
|
|
42
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('opens dropdown with Space on arrow button', () => {
|
|
46
|
+
const { container } = createComboboxTest()
|
|
47
|
+
|
|
48
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
49
|
+
fireEvent.keyDown(arrowButton!, { key: ' ' })
|
|
50
|
+
|
|
51
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('opens dropdown with ArrowDown on arrow button', () => {
|
|
55
|
+
const { container } = createComboboxTest()
|
|
56
|
+
|
|
57
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
58
|
+
fireEvent.keyDown(arrowButton!, { key: 'ArrowDown' })
|
|
59
|
+
|
|
60
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('toggles dropdown closed with Enter on arrow button', () => {
|
|
64
|
+
const { container } = createComboboxTest()
|
|
65
|
+
|
|
66
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
67
|
+
|
|
68
|
+
// Open
|
|
69
|
+
fireEvent.keyDown(arrowButton!, { key: 'Enter' })
|
|
70
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
71
|
+
|
|
72
|
+
// Close
|
|
73
|
+
fireEvent.keyDown(arrowButton!, { key: 'Enter' })
|
|
74
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('does not toggle on non-toggle keys', () => {
|
|
78
|
+
const { container } = createComboboxTest()
|
|
79
|
+
|
|
80
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
81
|
+
fireEvent.keyDown(arrowButton!, { key: 'Escape' })
|
|
82
|
+
|
|
83
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('submits value with Enter in text input', () => {
|
|
87
|
+
const handleValueChange = vi.fn()
|
|
88
|
+
const { container } = createComboboxTest({
|
|
89
|
+
allowUserInput: true,
|
|
90
|
+
options: getDefaultOptions(),
|
|
91
|
+
onValueChange: handleValueChange,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
95
|
+
fireEvent.focus(textInput)
|
|
96
|
+
|
|
97
|
+
textInput.value = 'apple'
|
|
98
|
+
fireEvent.change(textInput, { target: { value: 'apple' } })
|
|
99
|
+
fireEvent.keyDown(textInput, { key: 'Enter' })
|
|
100
|
+
|
|
101
|
+
expect(handleValueChange).toHaveBeenCalledWith(['apple'])
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('closes dropdown with Escape in text input', () => {
|
|
105
|
+
const { container } = createComboboxTest({
|
|
106
|
+
allowUserInput: true,
|
|
107
|
+
options: getDefaultOptions(),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
111
|
+
fireEvent.focus(textInput)
|
|
112
|
+
|
|
113
|
+
expect(textInput.getAttribute('aria-expanded')).toBe('true')
|
|
114
|
+
|
|
115
|
+
fireEvent.keyDown(textInput, { key: 'Escape' })
|
|
116
|
+
|
|
117
|
+
expect(textInput.getAttribute('aria-expanded')).toBe('false')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('does not open dropdown when disabled', () => {
|
|
121
|
+
const { container } = createComboboxTest({ disabled: true })
|
|
122
|
+
|
|
123
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
124
|
+
fireEvent.keyDown(arrowButton!, { key: 'Enter' })
|
|
125
|
+
|
|
126
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('selects option with Enter key on option element', () => {
|
|
130
|
+
const handleValueChange = vi.fn()
|
|
131
|
+
const { container } = createComboboxTest({
|
|
132
|
+
options: getDefaultOptions(),
|
|
133
|
+
onValueChange: handleValueChange,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
137
|
+
fireEvent.click(arrowButton!)
|
|
138
|
+
|
|
139
|
+
const option = container.querySelector('[data-value="apple"][role="option"]')
|
|
140
|
+
fireEvent.keyDown(option!, { key: 'Enter' })
|
|
141
|
+
|
|
142
|
+
expect(handleValueChange).toHaveBeenCalledWith(['apple'])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('selects option with Space key on option element', () => {
|
|
146
|
+
const handleValueChange = vi.fn()
|
|
147
|
+
const { container } = createComboboxTest({
|
|
148
|
+
options: getDefaultOptions(),
|
|
149
|
+
onValueChange: handleValueChange,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
153
|
+
fireEvent.click(arrowButton!)
|
|
154
|
+
|
|
155
|
+
const option = container.querySelector('[data-value="banana"][role="option"]')
|
|
156
|
+
fireEvent.keyDown(option!, { key: ' ' })
|
|
157
|
+
|
|
158
|
+
expect(handleValueChange).toHaveBeenCalledWith(['banana'])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('closes dropdown with Escape on option element', () => {
|
|
162
|
+
const { container } = createComboboxTest({
|
|
163
|
+
options: getDefaultOptions(),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
167
|
+
fireEvent.click(arrowButton!)
|
|
168
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
169
|
+
|
|
170
|
+
const option = container.querySelector('[data-value="apple"][role="option"]')
|
|
171
|
+
fireEvent.keyDown(option!, { key: 'Escape' })
|
|
172
|
+
|
|
173
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('adds value with comma separator in multiple mode', () => {
|
|
177
|
+
const handleValueChange = vi.fn()
|
|
178
|
+
const { container } = createComboboxTest({
|
|
179
|
+
allowUserInput: true,
|
|
180
|
+
multiple: true,
|
|
181
|
+
options: getDefaultOptions(),
|
|
182
|
+
onValueChange: handleValueChange,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
186
|
+
fireEvent.focus(textInput)
|
|
187
|
+
|
|
188
|
+
textInput.value = 'apple'
|
|
189
|
+
fireEvent.change(textInput, { target: { value: 'apple' } })
|
|
190
|
+
fireEvent.keyDown(textInput, { key: ',' })
|
|
191
|
+
|
|
192
|
+
expect(handleValueChange).toHaveBeenCalledWith(['apple'])
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
describe('Focus handling', () => {
|
|
197
|
+
test('opens dropdown on input focus', () => {
|
|
198
|
+
const { container } = createComboboxTest({
|
|
199
|
+
allowUserInput: true,
|
|
200
|
+
options: getDefaultOptions(),
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
204
|
+
fireEvent.focus(textInput)
|
|
205
|
+
|
|
206
|
+
expect(textInput.getAttribute('aria-expanded')).toBe('true')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('populates input with current value on focus in single-select', () => {
|
|
210
|
+
const { container } = createComboboxTest({
|
|
211
|
+
allowUserInput: true,
|
|
212
|
+
defaultValue: 'apple',
|
|
213
|
+
options: getDefaultOptions(),
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
217
|
+
fireEvent.focus(textInput)
|
|
218
|
+
|
|
219
|
+
expect(textInput.value).toBe('Apple')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('opens dropdown on input container click (hidden input mode)', () => {
|
|
223
|
+
const { container } = createComboboxTest({
|
|
224
|
+
options: getDefaultOptions(),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const inputDiv = container.querySelector('.pkt-combobox__input')
|
|
228
|
+
fireEvent.click(inputDiv!)
|
|
229
|
+
|
|
230
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
231
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('toggles dropdown on input container click (hidden input mode)', () => {
|
|
235
|
+
const { container } = createComboboxTest({
|
|
236
|
+
options: getDefaultOptions(),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const inputDiv = container.querySelector('.pkt-combobox__input')
|
|
240
|
+
|
|
241
|
+
fireEvent.click(inputDiv!)
|
|
242
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
243
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
244
|
+
|
|
245
|
+
fireEvent.click(inputDiv!)
|
|
246
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('does not open when disabled and input container is clicked', () => {
|
|
250
|
+
const { container } = createComboboxTest({ disabled: true })
|
|
251
|
+
|
|
252
|
+
const inputDiv = container.querySelector('.pkt-combobox__input')
|
|
253
|
+
fireEvent.click(inputDiv!)
|
|
254
|
+
|
|
255
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
256
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('focuses text input when placeholder is clicked', () => {
|
|
260
|
+
const { container } = createComboboxTest({
|
|
261
|
+
allowUserInput: true,
|
|
262
|
+
placeholder: 'Select...',
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
266
|
+
expect(textInput.placeholder).toBe('Select...')
|
|
267
|
+
fireEvent.click(textInput)
|
|
268
|
+
|
|
269
|
+
expect(document.activeElement).toBe(textInput)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('Focus-out behavior', () => {
|
|
274
|
+
test('closes dropdown when clicking outside combobox', () => {
|
|
275
|
+
const { container } = createComboboxTest({
|
|
276
|
+
options: getDefaultOptions(),
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
280
|
+
fireEvent.click(arrowButton!)
|
|
281
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
282
|
+
|
|
283
|
+
fireEvent.click(document.body)
|
|
284
|
+
|
|
285
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
test('adds custom value on focus-out when allowUserInput is on', () => {
|
|
289
|
+
const handleValueChange = vi.fn()
|
|
290
|
+
const { container } = createComboboxTest({
|
|
291
|
+
allowUserInput: true,
|
|
292
|
+
options: getDefaultOptions(),
|
|
293
|
+
onValueChange: handleValueChange,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
297
|
+
fireEvent.focus(textInput)
|
|
298
|
+
|
|
299
|
+
textInput.value = 'NewFruit'
|
|
300
|
+
fireEvent.change(textInput, { target: { value: 'NewFruit' } })
|
|
301
|
+
|
|
302
|
+
// Click outside to trigger close and process input
|
|
303
|
+
fireEvent.click(document.body)
|
|
304
|
+
|
|
305
|
+
expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test('selects matching option on focus-out when typeahead', () => {
|
|
309
|
+
const handleValueChange = vi.fn()
|
|
310
|
+
const { container } = createComboboxTest({
|
|
311
|
+
typeahead: true,
|
|
312
|
+
options: getDefaultOptions(),
|
|
313
|
+
onValueChange: handleValueChange,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
317
|
+
fireEvent.focus(textInput)
|
|
318
|
+
|
|
319
|
+
textInput.value = 'Apple'
|
|
320
|
+
fireEvent.change(textInput, { target: { value: 'Apple' } })
|
|
321
|
+
|
|
322
|
+
// Click outside to trigger close and process input
|
|
323
|
+
fireEvent.click(document.body)
|
|
324
|
+
|
|
325
|
+
expect(handleValueChange).toHaveBeenCalledWith(['apple'])
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
describe('Search and filtering', () => {
|
|
330
|
+
test('shows add-value banner when search has no exact match and allowUserInput is on', () => {
|
|
331
|
+
const { container } = createComboboxTest({
|
|
332
|
+
allowUserInput: true,
|
|
333
|
+
options: getDefaultOptions(),
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
337
|
+
fireEvent.focus(textInput)
|
|
338
|
+
|
|
339
|
+
fireEvent.input(textInput, { target: { value: 'NewFruit' } })
|
|
340
|
+
|
|
341
|
+
const addBanner = container.querySelector('.pkt-listbox__banner--new-option')
|
|
342
|
+
expect(addBanner).toBeInTheDocument()
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('renders search input in listbox when includeSearch is true', () => {
|
|
346
|
+
const { container } = createComboboxTest({ includeSearch: true })
|
|
347
|
+
|
|
348
|
+
const searchInput = container.querySelector('.pkt-listbox__search input')
|
|
349
|
+
expect(searchInput).toBeInTheDocument()
|
|
350
|
+
expect(searchInput?.getAttribute('role')).toBe('searchbox')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('sets search placeholder in listbox', () => {
|
|
354
|
+
const { container } = createComboboxTest({
|
|
355
|
+
includeSearch: true,
|
|
356
|
+
searchPlaceholder: 'Søk her...',
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
|
|
360
|
+
expect(searchInput.placeholder).toBe('Søk her...')
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
test('filters options via listbox search', () => {
|
|
364
|
+
const { container } = createComboboxTest({
|
|
365
|
+
includeSearch: true,
|
|
366
|
+
options: getDefaultOptions(),
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// Open dropdown
|
|
370
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
371
|
+
fireEvent.click(arrowButton!)
|
|
372
|
+
|
|
373
|
+
// Type in listbox search
|
|
374
|
+
const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
|
|
375
|
+
fireEvent.change(searchInput, { target: { value: 'app' } })
|
|
376
|
+
|
|
377
|
+
// Options should be filtered
|
|
378
|
+
const visibleOptions = container.querySelectorAll('.pkt-listbox__option')
|
|
379
|
+
expect(visibleOptions.length).toBeLessThan(4)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test('search input navigates to first option with ArrowDown', () => {
|
|
383
|
+
const { container } = createComboboxTest({
|
|
384
|
+
includeSearch: true,
|
|
385
|
+
options: getDefaultOptions(),
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// Open dropdown
|
|
389
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
390
|
+
fireEvent.click(arrowButton!)
|
|
391
|
+
|
|
392
|
+
const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
|
|
393
|
+
|
|
394
|
+
// ArrowDown in search should not throw
|
|
395
|
+
fireEvent.keyDown(searchInput, { key: 'ArrowDown' })
|
|
396
|
+
|
|
397
|
+
expect(searchInput).toBeInTheDocument()
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test('search input closes dropdown with Escape', () => {
|
|
401
|
+
const { container } = createComboboxTest({
|
|
402
|
+
includeSearch: true,
|
|
403
|
+
options: getDefaultOptions(),
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
407
|
+
fireEvent.click(arrowButton!)
|
|
408
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
|
|
409
|
+
|
|
410
|
+
const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
|
|
411
|
+
fireEvent.keyDown(searchInput, { key: 'Escape' })
|
|
412
|
+
|
|
413
|
+
expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('New option banner interaction', () => {
|
|
418
|
+
test('adds new option when clicking add-value banner', () => {
|
|
419
|
+
const handleValueChange = vi.fn()
|
|
420
|
+
const { container } = createComboboxTest({
|
|
421
|
+
allowUserInput: true,
|
|
422
|
+
options: getDefaultOptions(),
|
|
423
|
+
onValueChange: handleValueChange,
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
427
|
+
fireEvent.focus(textInput)
|
|
428
|
+
|
|
429
|
+
fireEvent.input(textInput, { target: { value: 'NewFruit' } })
|
|
430
|
+
|
|
431
|
+
const addBanner = container.querySelector('.pkt-listbox__banner--new-option')
|
|
432
|
+
expect(addBanner).toBeInTheDocument()
|
|
433
|
+
|
|
434
|
+
fireEvent.click(addBanner!)
|
|
435
|
+
|
|
436
|
+
expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('adds new option when pressing Enter on add-value banner', () => {
|
|
440
|
+
const handleValueChange = vi.fn()
|
|
441
|
+
const { container } = createComboboxTest({
|
|
442
|
+
allowUserInput: true,
|
|
443
|
+
options: getDefaultOptions(),
|
|
444
|
+
onValueChange: handleValueChange,
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
|
|
448
|
+
fireEvent.focus(textInput)
|
|
449
|
+
|
|
450
|
+
fireEvent.input(textInput, { target: { value: 'NewFruit' } })
|
|
451
|
+
|
|
452
|
+
const addBanner = container.querySelector('.pkt-listbox__banner--new-option')
|
|
453
|
+
expect(addBanner).toBeInTheDocument()
|
|
454
|
+
|
|
455
|
+
fireEvent.keyDown(addBanner!, { key: 'Enter' })
|
|
456
|
+
|
|
457
|
+
expect(handleValueChange).toHaveBeenCalledWith(['NewFruit'])
|
|
458
|
+
})
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
describe('Children as options', () => {
|
|
462
|
+
test('handles options from children correctly', () => {
|
|
463
|
+
const handleValueChange = vi.fn()
|
|
464
|
+
const { container } = render(
|
|
465
|
+
<PktCombobox id={comboboxId} label={label} onValueChange={handleValueChange}>
|
|
466
|
+
<option value="opt1">Option 1</option>
|
|
467
|
+
<option value="opt2">Option 2</option>
|
|
468
|
+
<option value="opt3">Option 3</option>
|
|
469
|
+
</PktCombobox>,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
473
|
+
fireEvent.click(arrowButton!)
|
|
474
|
+
|
|
475
|
+
const option = container.querySelector('[data-value="opt2"][role="option"]')
|
|
476
|
+
fireEvent.click(option!)
|
|
477
|
+
|
|
478
|
+
expect(handleValueChange).toHaveBeenCalledWith(['opt2'])
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
test('handles children with defaultValue', () => {
|
|
482
|
+
const { container } = render(
|
|
483
|
+
<PktCombobox id={comboboxId} label={label} defaultValue="opt1">
|
|
484
|
+
<option value="opt1">Option 1</option>
|
|
485
|
+
<option value="opt2">Option 2</option>
|
|
486
|
+
</PktCombobox>,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
const valueSpan = container.querySelector('.pkt-combobox__value')
|
|
490
|
+
expect(valueSpan?.textContent?.trim()).toBe('Option 1')
|
|
491
|
+
expect(getFormInputValue(container)).toBe('opt1')
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
test('handles children re-render with new options', () => {
|
|
495
|
+
const { container, rerender } = render(
|
|
496
|
+
<PktCombobox id={comboboxId} label={label}>
|
|
497
|
+
<option value="a">A</option>
|
|
498
|
+
<option value="b">B</option>
|
|
499
|
+
</PktCombobox>,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
let options = container.querySelectorAll('.pkt-listbox__option')
|
|
503
|
+
expect(options.length).toBe(2)
|
|
504
|
+
|
|
505
|
+
rerender(
|
|
506
|
+
<PktCombobox id={comboboxId} label={label}>
|
|
507
|
+
<option value="a">A</option>
|
|
508
|
+
<option value="b">B</option>
|
|
509
|
+
<option value="c">C</option>
|
|
510
|
+
</PktCombobox>,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
options = container.querySelectorAll('.pkt-listbox__option')
|
|
514
|
+
expect(options.length).toBe(3)
|
|
515
|
+
})
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
describe('Form reset', () => {
|
|
519
|
+
beforeEach(() => {
|
|
520
|
+
vi.useFakeTimers()
|
|
521
|
+
})
|
|
522
|
+
afterEach(() => {
|
|
523
|
+
vi.useRealTimers()
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
test('resets single value to defaultValue on form reset', () => {
|
|
527
|
+
const { container } = render(
|
|
528
|
+
<form>
|
|
529
|
+
<PktCombobox id={comboboxId} label={label} defaultValue="apple" options={getDefaultOptions()} />
|
|
530
|
+
</form>,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
// Select a different value
|
|
534
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
535
|
+
fireEvent.click(arrowButton!)
|
|
536
|
+
const bananaOption = container.querySelector('[data-value="banana"][role="option"]')
|
|
537
|
+
fireEvent.click(bananaOption!)
|
|
538
|
+
|
|
539
|
+
expect(getFormInputValue(container)).toBe('banana')
|
|
540
|
+
|
|
541
|
+
// Reset the form
|
|
542
|
+
const form = container.querySelector('form')!
|
|
543
|
+
act(() => {
|
|
544
|
+
fireEvent.reset(form)
|
|
545
|
+
vi.advanceTimersByTime(10)
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
expect(getFormInputValue(container)).toBe('apple')
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
test('resets multiple values to defaultValue on form reset', () => {
|
|
552
|
+
const { container } = render(
|
|
553
|
+
<form>
|
|
554
|
+
<PktCombobox
|
|
555
|
+
id={comboboxId}
|
|
556
|
+
label={label}
|
|
557
|
+
defaultValue={['apple', 'banana']}
|
|
558
|
+
multiple
|
|
559
|
+
options={getDefaultOptions()}
|
|
560
|
+
/>
|
|
561
|
+
</form>,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
// Select an additional value
|
|
565
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
566
|
+
fireEvent.click(arrowButton!)
|
|
567
|
+
const dateOption = container.querySelector('[data-value="date"][role="option"]')
|
|
568
|
+
fireEvent.click(dateOption!)
|
|
569
|
+
|
|
570
|
+
expect(getFormInputValue(container)).toBe('apple,banana,date')
|
|
571
|
+
|
|
572
|
+
// Reset the form
|
|
573
|
+
const form = container.querySelector('form')!
|
|
574
|
+
act(() => {
|
|
575
|
+
fireEvent.reset(form)
|
|
576
|
+
vi.advanceTimersByTime(10)
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
expect(getFormInputValue(container)).toBe('apple,banana')
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
test('resets to empty when no defaultValue on form reset', () => {
|
|
583
|
+
const { container } = render(
|
|
584
|
+
<form>
|
|
585
|
+
<PktCombobox id={comboboxId} label={label} options={getDefaultOptions()} />
|
|
586
|
+
</form>,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
// Select a value
|
|
590
|
+
const arrowButton = container.querySelector('.pkt-combobox__input')
|
|
591
|
+
fireEvent.click(arrowButton!)
|
|
592
|
+
const appleOption = container.querySelector('[data-value="apple"][role="option"]')
|
|
593
|
+
fireEvent.click(appleOption!)
|
|
594
|
+
|
|
595
|
+
expect(getFormInputValue(container)).toBe('apple')
|
|
596
|
+
|
|
597
|
+
// Reset the form
|
|
598
|
+
const form = container.querySelector('form')!
|
|
599
|
+
act(() => {
|
|
600
|
+
fireEvent.reset(form)
|
|
601
|
+
vi.advanceTimersByTime(10)
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
expect(getFormInputValue(container)).toBe('')
|
|
605
|
+
})
|
|
606
|
+
})
|
|
607
|
+
})
|