@oslokommune/punkt-elements 13.4.2 → 13.5.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.
@@ -28,6 +28,18 @@ afterEach(() => {
28
28
  document.body.innerHTML = ''
29
29
  })
30
30
 
31
+ // Global console.warn spy to suppress validation warnings in tests
32
+ let consoleWarnSpy: jest.SpyInstance
33
+ beforeEach(() => {
34
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
35
+ })
36
+
37
+ afterEach(() => {
38
+ if (consoleWarnSpy) {
39
+ consoleWarnSpy.mockRestore()
40
+ }
41
+ })
42
+
31
43
  describe('PktCard', () => {
32
44
  describe('Rendering and basic functionality', () => {
33
45
  test('renders without errors', async () => {
@@ -138,23 +150,25 @@ describe('PktCard', () => {
138
150
  })
139
151
 
140
152
  test('validates skin values and logs warnings for invalid skins', async () => {
141
- // Spy on console.warn to check if warning is logged
142
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
153
+ // Clear the global spy and create a new one for this specific test
154
+ consoleWarnSpy.mockRestore()
155
+ const localConsoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
143
156
 
144
157
  const container = await createCard(`skin="zebra"`)
145
158
  const card = container.querySelector('pkt-card') as PktCard
146
159
  await card.updateComplete
147
160
 
148
161
  // Should have logged a warning with the correct default value from spec
149
- expect(consoleSpy).toHaveBeenCalledWith(
162
+ expect(localConsoleSpy).toHaveBeenCalledWith(
150
163
  'Invalid skin value "zebra". Using default skin "outlined".',
151
164
  )
152
165
 
153
166
  // Should fall back to default from spec
154
167
  expect(card.skin).toBe('outlined')
155
168
 
156
- // Restore console.warn
157
- consoleSpy.mockRestore()
169
+ // Restore and recreate global spy for subsequent tests
170
+ localConsoleSpy.mockRestore()
171
+ consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
158
172
  })
159
173
 
160
174
  test('applies different layout properties correctly', async () => {
@@ -0,0 +1,193 @@
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 './datepicker'
8
+ import '../calendar/calendar'
9
+ import { PktDatepicker } from './datepicker'
10
+
11
+ const waitForCustomElements = async () => {
12
+ await customElements.whenDefined('pkt-datepicker')
13
+ await customElements.whenDefined('pkt-calendar')
14
+ }
15
+
16
+ // Helper function to create datepicker markup
17
+ const createDatepicker = async (datepickerProps = '') => {
18
+ const container = document.createElement('div')
19
+ container.innerHTML = `
20
+ <pkt-datepicker ${datepickerProps}></pkt-datepicker>
21
+ `
22
+ document.body.appendChild(container)
23
+ await waitForCustomElements()
24
+ return container
25
+ }
26
+
27
+ // Cleanup after each test
28
+ afterEach(() => {
29
+ document.body.innerHTML = ''
30
+ })
31
+
32
+ describe('PktDatepicker', () => {
33
+ describe('Event handling', () => {
34
+ test('dispatches change event when value changes', async () => {
35
+ const container = await createDatepicker()
36
+
37
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
38
+ await datepicker.updateComplete
39
+
40
+ let changeEventFired = false
41
+ datepicker.addEventListener('change', () => {
42
+ changeEventFired = true
43
+ })
44
+
45
+ const input = datepicker.querySelector('input') as HTMLInputElement
46
+ fireEvent.change(input, { target: { value: '2024-06-15' } })
47
+ fireEvent.blur(input)
48
+ await datepicker.updateComplete
49
+
50
+ expect(changeEventFired).toBe(true)
51
+ })
52
+
53
+ test('dispatches input event during typing', async () => {
54
+ const container = await createDatepicker()
55
+
56
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
57
+ await datepicker.updateComplete
58
+
59
+ let inputEventFired = false
60
+ datepicker.addEventListener('input', () => {
61
+ inputEventFired = true
62
+ })
63
+
64
+ const input = datepicker.querySelector('input') as HTMLInputElement
65
+ fireEvent.input(input, { target: { value: '2024-06-15' } })
66
+ await datepicker.updateComplete
67
+
68
+ expect(inputEventFired).toBe(true)
69
+ })
70
+
71
+ test('dispatches focus and blur events', async () => {
72
+ const container = await createDatepicker()
73
+
74
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
75
+ await datepicker.updateComplete
76
+
77
+ let focusEventFired = false
78
+ let blurEventFired = false
79
+
80
+ datepicker.addEventListener('focus', () => {
81
+ focusEventFired = true
82
+ })
83
+ datepicker.addEventListener('blur', () => {
84
+ blurEventFired = true
85
+ })
86
+
87
+ const input = datepicker.querySelector('input') as HTMLInputElement
88
+ fireEvent.focus(input)
89
+ await datepicker.updateComplete
90
+ fireEvent.blur(input)
91
+ await datepicker.updateComplete
92
+
93
+ expect(focusEventFired).toBe(true)
94
+ expect(blurEventFired).toBe(true)
95
+ })
96
+ })
97
+
98
+ describe('Accessibility', () => {
99
+ test('has no accessibility violations', async () => {
100
+ const container = await createDatepicker(
101
+ 'label="Test Datepicker" helptext="Select your date"',
102
+ )
103
+
104
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
105
+ await datepicker.updateComplete
106
+
107
+ const results = await axe(datepicker)
108
+ expect(results).toHaveNoViolations()
109
+ })
110
+
111
+ test('associates label with input correctly', async () => {
112
+ const container = await createDatepicker('id="test-datepicker" label="Test Label"')
113
+
114
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
115
+ await datepicker.updateComplete
116
+
117
+ const label = datepicker.querySelector('label')
118
+ const input = datepicker.querySelector('input')
119
+
120
+ expect(label).toHaveAttribute('for')
121
+ expect(input).toHaveAttribute('id')
122
+ expect(label?.getAttribute('for')).toBe(input?.getAttribute('id'))
123
+ })
124
+
125
+ test('has proper ARIA attributes for calendar button', async () => {
126
+ const container = await createDatepicker('label="Test Datepicker"')
127
+
128
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
129
+ await datepicker.updateComplete
130
+
131
+ const calendarButton = datepicker.querySelector('button[type="button"]')
132
+ expect(calendarButton).toBeInTheDocument()
133
+
134
+ // Check initial state
135
+ expect(datepicker.calendarOpen).toBe(false)
136
+
137
+ // Open calendar
138
+ fireEvent.click(calendarButton!)
139
+ await datepicker.updateComplete
140
+
141
+ expect(datepicker.calendarOpen).toBe(true)
142
+ })
143
+
144
+ test('provides proper screen reader announcements', async () => {
145
+ const container = await createDatepicker('label="Test Datepicker"')
146
+
147
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
148
+ await datepicker.updateComplete
149
+
150
+ const input = datepicker.querySelector('input')
151
+ expect(input).toHaveAttribute('aria-describedby')
152
+ })
153
+
154
+ test('handles focus management correctly', async () => {
155
+ const container = await createDatepicker()
156
+
157
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
158
+ await datepicker.updateComplete
159
+
160
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
161
+
162
+ // Focus should be manageable
163
+ if (calendarButton) {
164
+ calendarButton.focus()
165
+ expect(document.activeElement).toBe(calendarButton)
166
+ }
167
+ })
168
+
169
+ test('supports keyboard-only interaction', async () => {
170
+ const container = await createDatepicker()
171
+
172
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
173
+ await datepicker.updateComplete
174
+
175
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
176
+
177
+ // Should open with keyboard
178
+ if (calendarButton) {
179
+ calendarButton.focus()
180
+ fireEvent.keyDown(calendarButton, { key: 'Enter' })
181
+ await datepicker.updateComplete
182
+
183
+ expect(datepicker.calendarOpen).toBe(true)
184
+ }
185
+
186
+ // Should close with keyboard
187
+ fireEvent.keyDown(datepicker, { key: 'Escape' })
188
+ await datepicker.updateComplete
189
+
190
+ expect(datepicker.calendarOpen).toBe(false)
191
+ })
192
+ })
193
+ })
@@ -0,0 +1,322 @@
1
+ import '@testing-library/jest-dom'
2
+ import { toHaveNoViolations } from 'jest-axe'
3
+ import { fireEvent } from '@testing-library/dom'
4
+ import { parseISODateString } from '@/utils/dateutils'
5
+
6
+ expect.extend(toHaveNoViolations)
7
+
8
+ import './datepicker'
9
+ import '../calendar/calendar'
10
+ import { PktDatepicker } from './datepicker'
11
+ import { PktCalendar } from '../calendar/calendar'
12
+
13
+ const waitForCustomElements = async () => {
14
+ await customElements.whenDefined('pkt-datepicker')
15
+ await customElements.whenDefined('pkt-calendar')
16
+ }
17
+
18
+ // Helper function to create datepicker markup
19
+ const createDatepicker = async (datepickerProps = '') => {
20
+ const container = document.createElement('div')
21
+ container.innerHTML = `
22
+ <pkt-datepicker ${datepickerProps}></pkt-datepicker>
23
+ `
24
+ document.body.appendChild(container)
25
+ await waitForCustomElements()
26
+ return container
27
+ }
28
+
29
+ // Cleanup after each test
30
+ afterEach(() => {
31
+ document.body.innerHTML = ''
32
+ })
33
+
34
+ describe('PktDatepicker', () => {
35
+ describe('Rendering and basic functionality', () => {
36
+ test('renders without errors', async () => {
37
+ const container = await createDatepicker()
38
+
39
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
40
+ expect(datepicker).toBeInTheDocument()
41
+
42
+ await datepicker.updateComplete
43
+ expect(datepicker).toBeTruthy()
44
+ })
45
+
46
+ test('renders with correct structure', async () => {
47
+ const container = await createDatepicker('label="Test Datepicker"')
48
+
49
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
50
+ await datepicker.updateComplete
51
+
52
+ const inputWrapper = datepicker.querySelector('pkt-input-wrapper')
53
+ const input = datepicker.querySelector('input')
54
+ const calendarButton = datepicker.querySelector('button[type="button"]')
55
+
56
+ expect(inputWrapper).toBeInTheDocument()
57
+ expect(input).toBeInTheDocument()
58
+ expect(calendarButton).toBeInTheDocument()
59
+ })
60
+
61
+ test('renders calendar when opened', async () => {
62
+ const container = await createDatepicker('label="Test Datepicker"')
63
+
64
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
65
+ await datepicker.updateComplete
66
+
67
+ const calendarButton = datepicker.querySelector('button[type="button"]')
68
+ fireEvent.click(calendarButton!)
69
+ await datepicker.updateComplete
70
+
71
+ const calendar = datepicker.querySelector('pkt-calendar')
72
+ expect(calendar).toBeInTheDocument()
73
+ })
74
+
75
+ test('closes calendar when clicking outside', async () => {
76
+ const container = await createDatepicker('label="Test Datepicker"')
77
+
78
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
79
+ await datepicker.updateComplete
80
+
81
+ // Open calendar
82
+ const calendarButton = datepicker.querySelector('button[type="button"]')
83
+ fireEvent.click(calendarButton!)
84
+ await datepicker.updateComplete
85
+
86
+ // Click outside
87
+ fireEvent.click(document.body)
88
+ await datepicker.updateComplete
89
+
90
+ expect(datepicker.calendarOpen).toBe(false)
91
+ })
92
+
93
+ test('closes calendar when clicking outside', async () => {
94
+ const container = await createDatepicker('label="Test Datepicker"')
95
+
96
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
97
+ await datepicker.updateComplete
98
+
99
+ // Open calendar
100
+ const calendarButton = datepicker.querySelector('button[type="button"]')
101
+ fireEvent.click(calendarButton!)
102
+ await datepicker.updateComplete
103
+
104
+ // Click outside
105
+ fireEvent.click(document.body)
106
+ await datepicker.updateComplete
107
+
108
+ expect(datepicker.calendarOpen).toBe(false)
109
+ })
110
+ })
111
+
112
+ describe('Properties and attributes', () => {
113
+ test('applies default properties correctly', async () => {
114
+ const container = await createDatepicker('name="test" id="test"')
115
+
116
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
117
+ await datepicker.updateComplete
118
+
119
+ expect(datepicker.value).toBe('')
120
+ expect(datepicker.label).toBe('Datovelger')
121
+ expect(datepicker.multiple).toBe(false)
122
+ expect(datepicker.range).toBe(false)
123
+ expect(datepicker.maxlength).toBe(null)
124
+ expect(datepicker.showRangeLabels).toBe(false)
125
+ })
126
+
127
+ test('handles value property correctly', async () => {
128
+ const testDate = '2024-06-15'
129
+ const container = await createDatepicker(`value="${testDate}"`)
130
+
131
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
132
+ await datepicker.updateComplete
133
+
134
+ expect(datepicker.value).toBe(testDate)
135
+
136
+ const input = datepicker.querySelector('input') as HTMLInputElement
137
+ expect(input.value).toBeTruthy()
138
+ })
139
+
140
+ test('handles multiple values correctly', async () => {
141
+ const testDates = '2024-06-15,2024-06-20,2024-06-25'
142
+ const container = await createDatepicker(`value="${testDates}" multiple`)
143
+
144
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
145
+ await datepicker.updateComplete
146
+
147
+ expect(datepicker.value).toBe(testDates)
148
+ expect(datepicker.multiple).toBe(true)
149
+
150
+ // Should show tags for multiple values
151
+ const tags = datepicker.querySelectorAll('pkt-tag')
152
+ expect(tags.length).toBe(3)
153
+ })
154
+
155
+ test('handles range values correctly', async () => {
156
+ const rangeValue = '2024-06-15,2024-06-20'
157
+ const container = await createDatepicker(`value="${rangeValue}" range`)
158
+
159
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
160
+ await datepicker.updateComplete
161
+
162
+ expect(datepicker.value).toBe(rangeValue)
163
+ expect(datepicker.range).toBe(true)
164
+ })
165
+
166
+ test('handles label property correctly', async () => {
167
+ const testLabel = 'Select your date'
168
+ const container = await createDatepicker(`label="${testLabel}"`)
169
+
170
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
171
+ await datepicker.updateComplete
172
+
173
+ expect(datepicker.label).toBe(testLabel)
174
+
175
+ const label = datepicker.querySelector('label')
176
+ expect(label?.textContent).toContain(testLabel)
177
+ })
178
+
179
+ test('handles dateformat property correctly', async () => {
180
+ const customFormat = 'MM/dd/yyyy'
181
+ const container = await createDatepicker(
182
+ `dateformat="${customFormat}" value="2024-06-15" multiple`,
183
+ )
184
+
185
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
186
+ await datepicker.updateComplete
187
+
188
+ expect(datepicker.dateformat).toBe(customFormat)
189
+
190
+ // Multiple mode input should be empty (for new input)
191
+ const input = datepicker.querySelector('input') as HTMLInputElement
192
+ expect(input.value).toBe('')
193
+
194
+ // Selected dates should show in tags with custom format
195
+ const tag = datepicker.querySelector('pkt-tag time')
196
+ expect(tag).toBeInTheDocument()
197
+ if (tag) {
198
+ expect(tag.textContent).toMatch(/\d{2}\/\d{2}\/\d{4}/)
199
+ }
200
+ })
201
+
202
+ test('handles maxlength property correctly', async () => {
203
+ const container = await createDatepicker('multiple maxlength="3"')
204
+
205
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
206
+ await datepicker.updateComplete
207
+
208
+ expect(datepicker.maxlength).toBe(3)
209
+ })
210
+
211
+ test('handles disabled property correctly', async () => {
212
+ const container = await createDatepicker('disabled')
213
+
214
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
215
+ await datepicker.updateComplete
216
+
217
+ expect(datepicker.disabled).toBe(true)
218
+
219
+ const input = datepicker.querySelector('input') as HTMLInputElement
220
+ const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLButtonElement
221
+
222
+ expect(input.disabled).toBe(true)
223
+ expect(calendarButton.disabled).toBe(true)
224
+ })
225
+
226
+ test('handles currentmonth property correctly', async () => {
227
+ const testMonth = '2024-03-01'
228
+ const container = await createDatepicker(`currentmonth="${testMonth}"`)
229
+
230
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
231
+ await datepicker.updateComplete
232
+
233
+ // Open calendar to see the month
234
+ const calendarButton = datepicker.querySelector('button[type="button"]')
235
+ fireEvent.click(calendarButton!)
236
+ await datepicker.updateComplete
237
+
238
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
239
+ expect(calendar.currentmonth).toEqual(parseISODateString(testMonth))
240
+ })
241
+ })
242
+
243
+ describe('Calendar integration', () => {
244
+ test('passes properties to calendar correctly', async () => {
245
+ const container = await createDatepicker(
246
+ 'min="2024-06-01" max="2024-06-30" excludeweekdays="0,6"',
247
+ )
248
+
249
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
250
+ await datepicker.updateComplete
251
+
252
+ // Open calendar
253
+ const calendarButton = datepicker.querySelector('button[type="button"]')
254
+ fireEvent.click(calendarButton!)
255
+ await datepicker.updateComplete
256
+
257
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
258
+ expect(calendar.earliest).toBe('2024-06-01')
259
+ expect(calendar.latest).toBe('2024-06-30')
260
+ expect(calendar.excludeweekdays).toEqual(['0', '6'])
261
+ })
262
+
263
+ test('syncs selected dates between input and calendar', async () => {
264
+ const testDate = '2024-06-15'
265
+ const container = await createDatepicker(`value="${testDate}"`)
266
+
267
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
268
+ await datepicker.updateComplete
269
+
270
+ // Open calendar
271
+ const calendarButton = datepicker.querySelector('button[type="button"]')
272
+ fireEvent.click(calendarButton!)
273
+ await datepicker.updateComplete
274
+
275
+ const calendar = datepicker.querySelector('pkt-calendar') as PktCalendar
276
+ expect(calendar.selected).toContain(testDate)
277
+
278
+ const selectedDate = datepicker.querySelector('.pkt-calendar__date--selected')
279
+ expect(selectedDate).toBeInTheDocument()
280
+ })
281
+
282
+ test('updates input when date selected in calendar', async () => {
283
+ const container = await createDatepicker()
284
+
285
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
286
+ await datepicker.updateComplete
287
+
288
+ // Open calendar
289
+ const calendarButton = datepicker.querySelector('button[type="button"]')
290
+ fireEvent.click(calendarButton!)
291
+ await datepicker.updateComplete
292
+
293
+ // Select a date
294
+ const availableDate = datepicker.querySelector(
295
+ '.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
296
+ )
297
+ fireEvent.click(availableDate!)
298
+ await datepicker.updateComplete
299
+
300
+ // Input should be updated
301
+ const input = datepicker.querySelector('input') as HTMLInputElement
302
+ expect(input.value).toBeTruthy()
303
+ expect(datepicker.value).toBeTruthy()
304
+ })
305
+
306
+ test('shows current month correctly in calendar', async () => {
307
+ const currentMonth = '2024-06-01'
308
+ const container = await createDatepicker(`currentmonth="${currentMonth}"`)
309
+
310
+ const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
311
+ await datepicker.updateComplete
312
+
313
+ // Open calendar
314
+ const calendarButton = datepicker.querySelector('button[type="button"]')
315
+ fireEvent.click(calendarButton!)
316
+ await datepicker.updateComplete
317
+
318
+ const monthTitle = datepicker.querySelector('.pkt-calendar__month-title')
319
+ expect(monthTitle?.textContent).toContain('2024')
320
+ })
321
+ })
322
+ })