@oslokommune/punkt-elements 14.0.2 → 14.0.3
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/calendar-BtShW7ER.cjs +90 -0
- package/dist/{calendar-Bz27nuTP.js → calendar-yxjSI4wd.js} +766 -682
- package/dist/datepicker-D0q75U1Z.js +1463 -0
- package/dist/datepicker-DDV382Uu.cjs +271 -0
- package/dist/index.d.ts +118 -83
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +2 -2
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +3 -3
- package/package.json +2 -2
- package/src/components/calendar/calendar.ts +372 -414
- package/src/components/calendar/helpers/calendar-grid.ts +93 -0
- package/src/components/calendar/helpers/date-validation.ts +86 -0
- package/src/components/calendar/helpers/index.ts +49 -0
- package/src/components/calendar/helpers/keyboard-navigation.ts +54 -0
- package/src/components/calendar/helpers/selection-manager.ts +184 -0
- package/src/components/datepicker/datepicker-base.ts +151 -0
- package/src/components/datepicker/datepicker-multiple.ts +7 -114
- package/src/components/datepicker/datepicker-range.ts +21 -141
- package/src/components/datepicker/datepicker-single.ts +7 -115
- package/src/components/datepicker/datepicker-types.ts +56 -0
- package/src/components/datepicker/datepicker-utils.test.ts +730 -0
- package/src/components/datepicker/datepicker-utils.ts +338 -9
- package/src/components/datepicker/datepicker.ts +25 -1
- package/dist/calendar-Dz1Cnzx5.cjs +0 -115
- package/dist/datepicker-CnCOXI2x.cjs +0 -289
- package/dist/datepicker-DsqM01iU.js +0 -1355
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
valueUtils,
|
|
4
|
+
inputTypeUtils,
|
|
5
|
+
formUtils,
|
|
6
|
+
calendarUtils,
|
|
7
|
+
eventUtils,
|
|
8
|
+
cssUtils,
|
|
9
|
+
dateProcessingUtils,
|
|
10
|
+
keyboardUtils,
|
|
11
|
+
} from './datepicker-utils'
|
|
12
|
+
import { createRef } from 'lit/directives/ref.js'
|
|
13
|
+
|
|
14
|
+
describe('datepicker-utils', () => {
|
|
15
|
+
describe('valueUtils', () => {
|
|
16
|
+
describe('normalizeNameForMultiple', () => {
|
|
17
|
+
it('should return null when name is null', () => {
|
|
18
|
+
expect(valueUtils.normalizeNameForMultiple(null, false, false)).toBe(null)
|
|
19
|
+
expect(valueUtils.normalizeNameForMultiple(null, true, false)).toBe(null)
|
|
20
|
+
expect(valueUtils.normalizeNameForMultiple(null, false, true)).toBe(null)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should add [] suffix for multiple inputs without []', () => {
|
|
24
|
+
expect(valueUtils.normalizeNameForMultiple('dates', true, false)).toBe('dates[]')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should add [] suffix for range inputs without []', () => {
|
|
28
|
+
expect(valueUtils.normalizeNameForMultiple('daterange', false, true)).toBe('daterange[]')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should not add [] suffix when already present', () => {
|
|
32
|
+
expect(valueUtils.normalizeNameForMultiple('dates[]', true, false)).toBe('dates[]')
|
|
33
|
+
expect(valueUtils.normalizeNameForMultiple('range[]', false, true)).toBe('range[]')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should not modify name for single inputs', () => {
|
|
37
|
+
expect(valueUtils.normalizeNameForMultiple('singledate', false, false)).toBe('singledate')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('validateRangeOrder', () => {
|
|
42
|
+
it('should return true for incomplete ranges', () => {
|
|
43
|
+
expect(valueUtils.validateRangeOrder([])).toBe(true)
|
|
44
|
+
expect(valueUtils.validateRangeOrder(['2024-01-01'])).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should return true for valid date ranges', () => {
|
|
48
|
+
expect(valueUtils.validateRangeOrder(['2024-01-01', '2024-01-31'])).toBe(true)
|
|
49
|
+
expect(valueUtils.validateRangeOrder(['2024-01-01', '2024-01-01'])).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should return false for invalid date ranges', () => {
|
|
53
|
+
expect(valueUtils.validateRangeOrder(['2024-01-31', '2024-01-01'])).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('sortDates', () => {
|
|
58
|
+
it('should sort dates chronologically', () => {
|
|
59
|
+
const dates = ['2024-03-15', '2024-01-10', '2024-02-20']
|
|
60
|
+
const sorted = valueUtils.sortDates(dates)
|
|
61
|
+
expect(sorted).toEqual(['2024-01-10', '2024-02-20', '2024-03-15'])
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should handle empty array', () => {
|
|
65
|
+
expect(valueUtils.sortDates([])).toEqual([])
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('filterSelectableDates', () => {
|
|
70
|
+
it('should filter dates outside min/max range', () => {
|
|
71
|
+
const dates = ['2024-01-01', '2024-01-15', '2024-01-31']
|
|
72
|
+
const filtered = valueUtils.filterSelectableDates(dates, '2024-01-10', '2024-01-20')
|
|
73
|
+
expect(filtered).toEqual(['2024-01-15'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should filter excluded dates', () => {
|
|
77
|
+
const dates = ['2024-01-01', '2024-01-02', '2024-01-03']
|
|
78
|
+
const filtered = valueUtils.filterSelectableDates(
|
|
79
|
+
dates,
|
|
80
|
+
null,
|
|
81
|
+
null,
|
|
82
|
+
['2024-01-02'],
|
|
83
|
+
undefined,
|
|
84
|
+
)
|
|
85
|
+
expect(filtered).toEqual(['2024-01-01', '2024-01-03'])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should filter excluded weekdays', () => {
|
|
89
|
+
const dates = ['2024-01-01', '2024-01-06', '2024-01-07'] // Mon, Sat, Sun
|
|
90
|
+
const filtered = valueUtils.filterSelectableDates(dates, null, null, undefined, ['0', '6']) // Exclude weekends
|
|
91
|
+
expect(filtered).toEqual(['2024-01-01'])
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('inputTypeUtils', () => {
|
|
97
|
+
describe('getInputType', () => {
|
|
98
|
+
it('should return "text" for iOS devices', () => {
|
|
99
|
+
// Mock isIOS to return true
|
|
100
|
+
vi.mock('shared-utils/device-utils', () => ({
|
|
101
|
+
isIOS: () => true,
|
|
102
|
+
}))
|
|
103
|
+
// Note: This test is limited by how vitest handles module mocking
|
|
104
|
+
// In a real scenario, you'd need proper mocking setup
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should return "date" for non-iOS devices', () => {
|
|
108
|
+
// The function returns based on isIOS() which is mocked elsewhere
|
|
109
|
+
const type = inputTypeUtils.getInputType()
|
|
110
|
+
expect(['text', 'date']).toContain(type)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('formUtils', () => {
|
|
116
|
+
describe('submitForm', () => {
|
|
117
|
+
it('should call requestSubmit on the form if available', () => {
|
|
118
|
+
const mockForm = { requestSubmit: vi.fn() }
|
|
119
|
+
const element = {
|
|
120
|
+
internals: { form: mockForm },
|
|
121
|
+
} as any
|
|
122
|
+
|
|
123
|
+
formUtils.submitForm(element)
|
|
124
|
+
expect(mockForm.requestSubmit).toHaveBeenCalledOnce()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should not throw if form is not available', () => {
|
|
128
|
+
const element = {} as any
|
|
129
|
+
expect(() => formUtils.submitForm(element)).not.toThrow()
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('submitFormOrFallback', () => {
|
|
134
|
+
it('should submit form if internals has form', () => {
|
|
135
|
+
const mockForm = { requestSubmit: vi.fn() }
|
|
136
|
+
const fallback = vi.fn()
|
|
137
|
+
const internals = { form: mockForm }
|
|
138
|
+
|
|
139
|
+
formUtils.submitFormOrFallback(internals, fallback)
|
|
140
|
+
expect(mockForm.requestSubmit).toHaveBeenCalledOnce()
|
|
141
|
+
expect(fallback).not.toHaveBeenCalled()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should call fallback if no form available', () => {
|
|
145
|
+
const fallback = vi.fn()
|
|
146
|
+
formUtils.submitFormOrFallback(null, fallback)
|
|
147
|
+
expect(fallback).toHaveBeenCalledOnce()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should call fallback if internals has no form', () => {
|
|
151
|
+
const fallback = vi.fn()
|
|
152
|
+
const internals = {}
|
|
153
|
+
formUtils.submitFormOrFallback(internals, fallback)
|
|
154
|
+
expect(fallback).toHaveBeenCalledOnce()
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('validateDateInput', () => {
|
|
159
|
+
it('should return early if input has no value', () => {
|
|
160
|
+
const input = { value: '' } as HTMLInputElement
|
|
161
|
+
const internals = { setValidity: vi.fn() }
|
|
162
|
+
formUtils.validateDateInput(input, internals)
|
|
163
|
+
expect(internals.setValidity).not.toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should set rangeUnderflow validity when value is below minimum', () => {
|
|
167
|
+
const input = { value: '2024-01-01' } as HTMLInputElement
|
|
168
|
+
const internals = { setValidity: vi.fn() }
|
|
169
|
+
const strings = { forms: { messages: { rangeUnderflow: 'Too early' } } }
|
|
170
|
+
|
|
171
|
+
formUtils.validateDateInput(input, internals, '2024-01-10', null, strings)
|
|
172
|
+
expect(internals.setValidity).toHaveBeenCalledWith(
|
|
173
|
+
{ rangeUnderflow: true },
|
|
174
|
+
'Too early',
|
|
175
|
+
input,
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should set rangeOverflow validity when value is above maximum', () => {
|
|
180
|
+
const input = { value: '2024-01-31' } as HTMLInputElement
|
|
181
|
+
const internals = { setValidity: vi.fn() }
|
|
182
|
+
const strings = { forms: { messages: { rangeOverflow: 'Too late' } } }
|
|
183
|
+
|
|
184
|
+
formUtils.validateDateInput(input, internals, null, '2024-01-20', strings)
|
|
185
|
+
expect(internals.setValidity).toHaveBeenCalledWith(
|
|
186
|
+
{ rangeOverflow: true },
|
|
187
|
+
'Too late',
|
|
188
|
+
input,
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should use default messages when strings not provided', () => {
|
|
193
|
+
const input = { value: '2024-01-01' } as HTMLInputElement
|
|
194
|
+
const internals = { setValidity: vi.fn() }
|
|
195
|
+
|
|
196
|
+
formUtils.validateDateInput(input, internals, '2024-01-10', null)
|
|
197
|
+
expect(internals.setValidity).toHaveBeenCalledWith(
|
|
198
|
+
{ rangeUnderflow: true },
|
|
199
|
+
'Value is below minimum',
|
|
200
|
+
input,
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('calendarUtils', () => {
|
|
207
|
+
describe('addToSelected', () => {
|
|
208
|
+
it('should return early if target has no value', () => {
|
|
209
|
+
const event = { target: { value: '' } } as any
|
|
210
|
+
const calendarRef = createRef()
|
|
211
|
+
calendarUtils.addToSelected(event, calendarRef as any)
|
|
212
|
+
// Should not throw
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should clear input value after processing', () => {
|
|
216
|
+
const mockCalendar = { handleDateSelect: vi.fn() }
|
|
217
|
+
const target = { value: '2024-01-15' }
|
|
218
|
+
const event = { target } as any
|
|
219
|
+
const calendarRef = { value: mockCalendar } as any
|
|
220
|
+
|
|
221
|
+
calendarUtils.addToSelected(event, calendarRef)
|
|
222
|
+
expect(target.value).toBe('')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should call handleDateSelect with valid date', () => {
|
|
226
|
+
const mockCalendar = { handleDateSelect: vi.fn() }
|
|
227
|
+
const target = { value: '2024-01-15' }
|
|
228
|
+
const event = { target } as any
|
|
229
|
+
const calendarRef = { value: mockCalendar } as any
|
|
230
|
+
|
|
231
|
+
calendarUtils.addToSelected(event, calendarRef)
|
|
232
|
+
expect(mockCalendar.handleDateSelect).toHaveBeenCalled()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should respect min/max constraints', () => {
|
|
236
|
+
const mockCalendar = { handleDateSelect: vi.fn() }
|
|
237
|
+
const target = { value: '2024-01-05' }
|
|
238
|
+
const event = { target } as any
|
|
239
|
+
const calendarRef = { value: mockCalendar } as any
|
|
240
|
+
|
|
241
|
+
calendarUtils.addToSelected(event, calendarRef, '2024-01-10', '2024-01-20')
|
|
242
|
+
expect(mockCalendar.handleDateSelect).not.toHaveBeenCalled()
|
|
243
|
+
expect(target.value).toBe('')
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('handleCalendarPosition', () => {
|
|
248
|
+
it('should return early if refs are not available', () => {
|
|
249
|
+
const popupRef = createRef()
|
|
250
|
+
const inputRef = createRef()
|
|
251
|
+
expect(() => calendarUtils.handleCalendarPosition(popupRef as any, inputRef as any)).not.toThrow()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('should position calendar below input by default', () => {
|
|
255
|
+
const mockPopup = { style: { top: '' }, getBoundingClientRect: () => ({ height: 300 }) }
|
|
256
|
+
const mockInput = {
|
|
257
|
+
getBoundingClientRect: () => ({ height: 40, top: 100 }),
|
|
258
|
+
parentElement: null,
|
|
259
|
+
}
|
|
260
|
+
const popupRef = { value: mockPopup } as any
|
|
261
|
+
const inputRef = { value: mockInput } as any
|
|
262
|
+
|
|
263
|
+
calendarUtils.handleCalendarPosition(popupRef, inputRef)
|
|
264
|
+
expect(mockPopup.style.top).toBe('100%')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should position calendar above input if not enough space below', () => {
|
|
268
|
+
const mockPopup = { style: { top: '' }, getBoundingClientRect: () => ({ height: 400 }) }
|
|
269
|
+
const mockInput = {
|
|
270
|
+
getBoundingClientRect: () => ({ height: 40, top: window.innerHeight - 100 }),
|
|
271
|
+
parentElement: null,
|
|
272
|
+
}
|
|
273
|
+
const popupRef = { value: mockPopup } as any
|
|
274
|
+
const inputRef = { value: mockInput } as any
|
|
275
|
+
|
|
276
|
+
calendarUtils.handleCalendarPosition(popupRef, inputRef)
|
|
277
|
+
expect(mockPopup.style.top).toContain('calc(100%')
|
|
278
|
+
expect(mockPopup.style.top).toContain('px')
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('should account for counter when hasCounter is true', () => {
|
|
282
|
+
const mockPopup = { style: { top: '' }, getBoundingClientRect: () => ({ height: 300 }) }
|
|
283
|
+
const mockInput = {
|
|
284
|
+
getBoundingClientRect: () => ({ height: 40, top: 100 }),
|
|
285
|
+
parentElement: null,
|
|
286
|
+
}
|
|
287
|
+
const popupRef = { value: mockPopup } as any
|
|
288
|
+
const inputRef = { value: mockInput } as any
|
|
289
|
+
|
|
290
|
+
calendarUtils.handleCalendarPosition(popupRef, inputRef, true)
|
|
291
|
+
expect(mockPopup.style.top).toBe('calc(100% - 30px)')
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('eventUtils', () => {
|
|
297
|
+
describe('createDocumentClickListener', () => {
|
|
298
|
+
it('should return a function', () => {
|
|
299
|
+
const listener = eventUtils.createDocumentClickListener(
|
|
300
|
+
createRef() as any,
|
|
301
|
+
null,
|
|
302
|
+
createRef() as any,
|
|
303
|
+
() => true,
|
|
304
|
+
vi.fn(),
|
|
305
|
+
vi.fn(),
|
|
306
|
+
)
|
|
307
|
+
expect(typeof listener).toBe('function')
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('should call onBlur and hideCalendar when clicking outside', () => {
|
|
311
|
+
const onBlur = vi.fn()
|
|
312
|
+
const hideCalendar = vi.fn()
|
|
313
|
+
const inputRef = { value: { contains: () => false } } as any
|
|
314
|
+
const btnRef = { value: { contains: () => false } } as any
|
|
315
|
+
|
|
316
|
+
const listener = eventUtils.createDocumentClickListener(
|
|
317
|
+
inputRef,
|
|
318
|
+
null,
|
|
319
|
+
btnRef,
|
|
320
|
+
() => true,
|
|
321
|
+
onBlur,
|
|
322
|
+
hideCalendar,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
const event = {
|
|
326
|
+
target: document.createElement('div'),
|
|
327
|
+
} as any
|
|
328
|
+
|
|
329
|
+
listener(event)
|
|
330
|
+
expect(onBlur).toHaveBeenCalledOnce()
|
|
331
|
+
expect(hideCalendar).toHaveBeenCalledOnce()
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('should not call handlers when clicking inside input', () => {
|
|
335
|
+
const onBlur = vi.fn()
|
|
336
|
+
const hideCalendar = vi.fn()
|
|
337
|
+
const inputRef = { value: { contains: () => true } } as any
|
|
338
|
+
const btnRef = { value: { contains: () => false } } as any
|
|
339
|
+
|
|
340
|
+
const listener = eventUtils.createDocumentClickListener(
|
|
341
|
+
inputRef,
|
|
342
|
+
null,
|
|
343
|
+
btnRef,
|
|
344
|
+
() => true,
|
|
345
|
+
onBlur,
|
|
346
|
+
hideCalendar,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
const event = {
|
|
350
|
+
target: document.createElement('div'),
|
|
351
|
+
} as any
|
|
352
|
+
|
|
353
|
+
listener(event)
|
|
354
|
+
expect(onBlur).not.toHaveBeenCalled()
|
|
355
|
+
expect(hideCalendar).not.toHaveBeenCalled()
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe('createDocumentKeydownListener', () => {
|
|
360
|
+
it('should return a function', () => {
|
|
361
|
+
const listener = eventUtils.createDocumentKeydownListener(() => true, vi.fn())
|
|
362
|
+
expect(typeof listener).toBe('function')
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should call hideCalendar on Escape key', () => {
|
|
366
|
+
const hideCalendar = vi.fn()
|
|
367
|
+
const listener = eventUtils.createDocumentKeydownListener(() => true, hideCalendar)
|
|
368
|
+
|
|
369
|
+
const event = { key: 'Escape' } as KeyboardEvent
|
|
370
|
+
listener(event)
|
|
371
|
+
expect(hideCalendar).toHaveBeenCalledOnce()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('should not call hideCalendar if calendar is not open', () => {
|
|
375
|
+
const hideCalendar = vi.fn()
|
|
376
|
+
const listener = eventUtils.createDocumentKeydownListener(() => false, hideCalendar)
|
|
377
|
+
|
|
378
|
+
const event = { key: 'Escape' } as KeyboardEvent
|
|
379
|
+
listener(event)
|
|
380
|
+
expect(hideCalendar).not.toHaveBeenCalled()
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should not call hideCalendar on other keys', () => {
|
|
384
|
+
const hideCalendar = vi.fn()
|
|
385
|
+
const listener = eventUtils.createDocumentKeydownListener(() => true, hideCalendar)
|
|
386
|
+
|
|
387
|
+
const event = { key: 'Enter' } as KeyboardEvent
|
|
388
|
+
listener(event)
|
|
389
|
+
expect(hideCalendar).not.toHaveBeenCalled()
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
describe('handleFocusOut', () => {
|
|
394
|
+
it('should call onBlur and hideCalendar when focus leaves element', () => {
|
|
395
|
+
const onBlur = vi.fn()
|
|
396
|
+
const hideCalendar = vi.fn()
|
|
397
|
+
const element = { contains: () => false } as any
|
|
398
|
+
const event = { target: document.createElement('div') } as any
|
|
399
|
+
|
|
400
|
+
eventUtils.handleFocusOut(event, element, onBlur, hideCalendar)
|
|
401
|
+
expect(onBlur).toHaveBeenCalledOnce()
|
|
402
|
+
expect(hideCalendar).toHaveBeenCalledOnce()
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should not call handlers when focus stays within element', () => {
|
|
406
|
+
const onBlur = vi.fn()
|
|
407
|
+
const hideCalendar = vi.fn()
|
|
408
|
+
const element = { contains: () => true } as any
|
|
409
|
+
const event = { target: document.createElement('div') } as any
|
|
410
|
+
|
|
411
|
+
eventUtils.handleFocusOut(event, element, onBlur, hideCalendar)
|
|
412
|
+
expect(onBlur).not.toHaveBeenCalled()
|
|
413
|
+
expect(hideCalendar).not.toHaveBeenCalled()
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe('cssUtils', () => {
|
|
419
|
+
describe('getInputClasses', () => {
|
|
420
|
+
it('should return base classes', () => {
|
|
421
|
+
const classes = cssUtils.getInputClasses(false, false, false, false)
|
|
422
|
+
expect(classes['pkt-input']).toBe(true)
|
|
423
|
+
expect(classes['pkt-datepicker__input']).toBe(true)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('should include fullwidth class when fullwidth is true', () => {
|
|
427
|
+
const classes = cssUtils.getInputClasses(true, false, false, false)
|
|
428
|
+
expect(classes['pkt-input--fullwidth']).toBe(true)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it('should include hasrangelabels class when showRangeLabels is true', () => {
|
|
432
|
+
const classes = cssUtils.getInputClasses(false, true, false, false)
|
|
433
|
+
expect(classes['pkt-datepicker--hasrangelabels']).toBe(true)
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
it('should include multiple class when multiple is true', () => {
|
|
437
|
+
const classes = cssUtils.getInputClasses(false, false, true, false)
|
|
438
|
+
expect(classes['pkt-datepicker--multiple']).toBe(true)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should include range class when range is true', () => {
|
|
442
|
+
const classes = cssUtils.getInputClasses(false, false, false, true)
|
|
443
|
+
expect(classes['pkt-datepicker--range']).toBe(true)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('should include ios-readonly-hack when readonly is false and inputType is text', () => {
|
|
447
|
+
const classes = cssUtils.getInputClasses(false, false, false, false, false, 'text')
|
|
448
|
+
expect(classes['ios-readonly-hack']).toBe(true)
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('getButtonClasses', () => {
|
|
453
|
+
it('should return button classes', () => {
|
|
454
|
+
const classes = cssUtils.getButtonClasses()
|
|
455
|
+
expect(classes['pkt-input-icon']).toBe(true)
|
|
456
|
+
expect(classes['pkt-btn']).toBe(true)
|
|
457
|
+
expect(classes['pkt-btn--icon-only']).toBe(true)
|
|
458
|
+
expect(classes['pkt-btn--tertiary']).toBe(true)
|
|
459
|
+
expect(classes['pkt-datepicker__calendar-button']).toBe(true)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
describe('getRangeLabelClasses', () => {
|
|
464
|
+
it('should return correct classes when showRangeLabels is true', () => {
|
|
465
|
+
const classes = cssUtils.getRangeLabelClasses(true)
|
|
466
|
+
expect(classes['pkt-input-prefix']).toBe(true)
|
|
467
|
+
expect(classes['pkt-hide']).toBe(false)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('should return correct classes when showRangeLabels is false', () => {
|
|
471
|
+
const classes = cssUtils.getRangeLabelClasses(false)
|
|
472
|
+
expect(classes['pkt-input-prefix']).toBe(false)
|
|
473
|
+
expect(classes['pkt-hide']).toBe(true)
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
describe('dateProcessingUtils', () => {
|
|
479
|
+
describe('processDateSelection', () => {
|
|
480
|
+
it('should return first date for single selection', () => {
|
|
481
|
+
const result = dateProcessingUtils.processDateSelection(['2024-01-15'], false, false)
|
|
482
|
+
expect(result).toBe('2024-01-15')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('should return empty string when no dates for single selection', () => {
|
|
486
|
+
const result = dateProcessingUtils.processDateSelection([], false, false)
|
|
487
|
+
expect(result).toBe('')
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
it('should return comma-separated dates for multiple selection', () => {
|
|
491
|
+
const result = dateProcessingUtils.processDateSelection(
|
|
492
|
+
['2024-01-15', '2024-01-20'],
|
|
493
|
+
true,
|
|
494
|
+
false,
|
|
495
|
+
)
|
|
496
|
+
expect(result).toBe('2024-01-15,2024-01-20')
|
|
497
|
+
})
|
|
498
|
+
|
|
499
|
+
it('should return comma-separated dates for range selection', () => {
|
|
500
|
+
const result = dateProcessingUtils.processDateSelection(
|
|
501
|
+
['2024-01-15', '2024-01-20'],
|
|
502
|
+
false,
|
|
503
|
+
true,
|
|
504
|
+
)
|
|
505
|
+
expect(result).toBe('2024-01-15,2024-01-20')
|
|
506
|
+
})
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
describe('updateInputValues', () => {
|
|
510
|
+
it('should return early if inputRef has no value', () => {
|
|
511
|
+
const inputRef = { value: null } as any
|
|
512
|
+
expect(() =>
|
|
513
|
+
dateProcessingUtils.updateInputValues(inputRef, null, [], false, false, vi.fn()),
|
|
514
|
+
).not.toThrow()
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('should update both inputs for range', () => {
|
|
518
|
+
const input = { value: '' } as any
|
|
519
|
+
const inputTo = { value: '' } as any
|
|
520
|
+
const inputRef = { value: input } as any
|
|
521
|
+
const inputRefTo = { value: inputTo } as any
|
|
522
|
+
const manageValidity = vi.fn()
|
|
523
|
+
|
|
524
|
+
dateProcessingUtils.updateInputValues(
|
|
525
|
+
inputRef,
|
|
526
|
+
inputRefTo,
|
|
527
|
+
['2024-01-15', '2024-01-20'],
|
|
528
|
+
true,
|
|
529
|
+
false,
|
|
530
|
+
manageValidity,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
expect(input.value).toBe('2024-01-15')
|
|
534
|
+
expect(inputTo.value).toBe('2024-01-20')
|
|
535
|
+
expect(manageValidity).toHaveBeenCalledTimes(2)
|
|
536
|
+
})
|
|
537
|
+
|
|
538
|
+
it('should update single input for non-multiple, non-range', () => {
|
|
539
|
+
const input = { value: '' } as any
|
|
540
|
+
const inputRef = { value: input } as any
|
|
541
|
+
const manageValidity = vi.fn()
|
|
542
|
+
|
|
543
|
+
dateProcessingUtils.updateInputValues(
|
|
544
|
+
inputRef,
|
|
545
|
+
null,
|
|
546
|
+
['2024-01-15'],
|
|
547
|
+
false,
|
|
548
|
+
false,
|
|
549
|
+
manageValidity,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
expect(input.value).toBe('2024-01-15')
|
|
553
|
+
expect(manageValidity).toHaveBeenCalledOnce()
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
it('should not update input for multiple selection', () => {
|
|
557
|
+
const input = { value: 'initial' } as any
|
|
558
|
+
const inputRef = { value: input } as any
|
|
559
|
+
const manageValidity = vi.fn()
|
|
560
|
+
|
|
561
|
+
dateProcessingUtils.updateInputValues(
|
|
562
|
+
inputRef,
|
|
563
|
+
null,
|
|
564
|
+
['2024-01-15'],
|
|
565
|
+
false,
|
|
566
|
+
true,
|
|
567
|
+
manageValidity,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
expect(input.value).toBe('initial')
|
|
571
|
+
expect(manageValidity).not.toHaveBeenCalled()
|
|
572
|
+
})
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
describe('processRangeBlur', () => {
|
|
576
|
+
it('should call manageValidity and handleDateSelect when target has value', () => {
|
|
577
|
+
const target = { value: '2024-01-15' } as any
|
|
578
|
+
const event = { target } as any
|
|
579
|
+
const mockCalendar = { handleDateSelect: vi.fn() }
|
|
580
|
+
const calendarRef = { value: mockCalendar } as any
|
|
581
|
+
const clearInputValue = vi.fn()
|
|
582
|
+
const manageValidity = vi.fn()
|
|
583
|
+
|
|
584
|
+
dateProcessingUtils.processRangeBlur(
|
|
585
|
+
event,
|
|
586
|
+
['2024-01-10', '2024-01-20'],
|
|
587
|
+
calendarRef,
|
|
588
|
+
clearInputValue,
|
|
589
|
+
manageValidity,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
expect(manageValidity).toHaveBeenCalledWith(target)
|
|
593
|
+
expect(clearInputValue).not.toHaveBeenCalled()
|
|
594
|
+
expect(mockCalendar.handleDateSelect).toHaveBeenCalled()
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it('should clear input value when target is empty but values[0] exists', () => {
|
|
598
|
+
const target = { value: '' } as any
|
|
599
|
+
const event = { target } as any
|
|
600
|
+
const mockCalendar = { handleDateSelect: vi.fn() }
|
|
601
|
+
const calendarRef = { value: mockCalendar } as any
|
|
602
|
+
const clearInputValue = vi.fn()
|
|
603
|
+
const manageValidity = vi.fn()
|
|
604
|
+
|
|
605
|
+
dateProcessingUtils.processRangeBlur(
|
|
606
|
+
event,
|
|
607
|
+
['2024-01-10'],
|
|
608
|
+
calendarRef,
|
|
609
|
+
clearInputValue,
|
|
610
|
+
manageValidity,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
expect(clearInputValue).toHaveBeenCalled()
|
|
614
|
+
expect(manageValidity).not.toHaveBeenCalled()
|
|
615
|
+
expect(mockCalendar.handleDateSelect).not.toHaveBeenCalled()
|
|
616
|
+
})
|
|
617
|
+
})
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
describe('keyboardUtils', () => {
|
|
621
|
+
describe('handleInputKeydown', () => {
|
|
622
|
+
it('should call toggleCalendar on Space key', () => {
|
|
623
|
+
const toggleCalendar = vi.fn()
|
|
624
|
+
const event = { key: ' ', preventDefault: vi.fn() } as any
|
|
625
|
+
|
|
626
|
+
keyboardUtils.handleInputKeydown(event, toggleCalendar)
|
|
627
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
628
|
+
expect(toggleCalendar).toHaveBeenCalledWith(event)
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
it('should call submitForm on Enter key when provided', () => {
|
|
632
|
+
const toggleCalendar = vi.fn()
|
|
633
|
+
const submitForm = vi.fn()
|
|
634
|
+
const event = { key: 'Enter', preventDefault: vi.fn() } as any
|
|
635
|
+
|
|
636
|
+
keyboardUtils.handleInputKeydown(event, toggleCalendar, submitForm)
|
|
637
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
638
|
+
expect(submitForm).toHaveBeenCalled()
|
|
639
|
+
expect(toggleCalendar).not.toHaveBeenCalled()
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
it('should call focusNextInput on Enter when submitForm not provided', () => {
|
|
643
|
+
const toggleCalendar = vi.fn()
|
|
644
|
+
const focusNextInput = vi.fn()
|
|
645
|
+
const event = { key: 'Enter', preventDefault: vi.fn() } as any
|
|
646
|
+
|
|
647
|
+
keyboardUtils.handleInputKeydown(event, toggleCalendar, undefined, focusNextInput)
|
|
648
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
649
|
+
expect(focusNextInput).toHaveBeenCalled()
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
it('should call blurInput on Enter when neither submitForm nor focusNextInput provided', () => {
|
|
653
|
+
const toggleCalendar = vi.fn()
|
|
654
|
+
const blurInput = vi.fn()
|
|
655
|
+
const event = { key: 'Enter', preventDefault: vi.fn() } as any
|
|
656
|
+
|
|
657
|
+
keyboardUtils.handleInputKeydown(
|
|
658
|
+
event,
|
|
659
|
+
toggleCalendar,
|
|
660
|
+
undefined,
|
|
661
|
+
undefined,
|
|
662
|
+
blurInput,
|
|
663
|
+
)
|
|
664
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
665
|
+
expect(blurInput).toHaveBeenCalled()
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
it('should call commaHandler on comma key when provided', () => {
|
|
669
|
+
const toggleCalendar = vi.fn()
|
|
670
|
+
const commaHandler = vi.fn()
|
|
671
|
+
const event = { key: ',', preventDefault: vi.fn() } as any
|
|
672
|
+
|
|
673
|
+
keyboardUtils.handleInputKeydown(
|
|
674
|
+
event,
|
|
675
|
+
toggleCalendar,
|
|
676
|
+
undefined,
|
|
677
|
+
undefined,
|
|
678
|
+
undefined,
|
|
679
|
+
commaHandler,
|
|
680
|
+
)
|
|
681
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
682
|
+
expect(commaHandler).toHaveBeenCalledWith(event)
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
it('should call blurInput on comma key when commaHandler not provided', () => {
|
|
686
|
+
const toggleCalendar = vi.fn()
|
|
687
|
+
const blurInput = vi.fn()
|
|
688
|
+
const event = { key: ',', preventDefault: vi.fn() } as any
|
|
689
|
+
|
|
690
|
+
keyboardUtils.handleInputKeydown(
|
|
691
|
+
event,
|
|
692
|
+
toggleCalendar,
|
|
693
|
+
undefined,
|
|
694
|
+
undefined,
|
|
695
|
+
blurInput,
|
|
696
|
+
)
|
|
697
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
698
|
+
expect(blurInput).toHaveBeenCalled()
|
|
699
|
+
})
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
describe('handleButtonKeydown', () => {
|
|
703
|
+
it('should call toggleCalendar on Enter key', () => {
|
|
704
|
+
const toggleCalendar = vi.fn()
|
|
705
|
+
const event = { key: 'Enter', preventDefault: vi.fn() } as any
|
|
706
|
+
|
|
707
|
+
keyboardUtils.handleButtonKeydown(event, toggleCalendar)
|
|
708
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
709
|
+
expect(toggleCalendar).toHaveBeenCalledWith(event)
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('should call toggleCalendar on Space key', () => {
|
|
713
|
+
const toggleCalendar = vi.fn()
|
|
714
|
+
const event = { key: ' ', preventDefault: vi.fn() } as any
|
|
715
|
+
|
|
716
|
+
keyboardUtils.handleButtonKeydown(event, toggleCalendar)
|
|
717
|
+
expect(event.preventDefault).toHaveBeenCalled()
|
|
718
|
+
expect(toggleCalendar).toHaveBeenCalledWith(event)
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it('should not call toggleCalendar on other keys', () => {
|
|
722
|
+
const toggleCalendar = vi.fn()
|
|
723
|
+
const event = { key: 'a', preventDefault: vi.fn() } as any
|
|
724
|
+
|
|
725
|
+
keyboardUtils.handleButtonKeydown(event, toggleCalendar)
|
|
726
|
+
expect(toggleCalendar).not.toHaveBeenCalled()
|
|
727
|
+
})
|
|
728
|
+
})
|
|
729
|
+
})
|
|
730
|
+
})
|