@oslokommune/punkt-elements 13.4.2 → 13.5.1
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 +35 -0
- package/dist/calendar-32W9p9uc.cjs +115 -0
- package/dist/{calendar-DevQhOup.js → calendar-CJSxvwAq.js} +353 -340
- package/dist/{card-Dtw26f7i.js → card-BDz4RWxK.js} +1 -1
- package/dist/{card-BUITGoqX.cjs → card-DBlFf1ry.cjs} +1 -1
- package/dist/{datepicker-CYOn3tRm.js → datepicker-BJKJBoy_.js} +102 -59
- package/dist/datepicker-CmTrG5GE.cjs +164 -0
- package/dist/{heading-D6jXE_Mz.js → heading-Bdh9absf.js} +22 -22
- package/dist/heading-CNycsyMj.cjs +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-card.cjs +1 -1
- package/dist/pkt-card.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +1 -1
- package/dist/pkt-heading.cjs +1 -1
- package/dist/pkt-heading.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +5 -5
- package/package.json +3 -3
- package/src/components/calendar/calendar.accessibility.test.ts +111 -0
- package/src/components/calendar/calendar.constraints.test.ts +110 -0
- package/src/components/calendar/calendar.core.test.ts +367 -0
- package/src/components/calendar/calendar.interaction.test.ts +139 -0
- package/src/components/calendar/calendar.selection.test.ts +273 -0
- package/src/components/calendar/calendar.ts +74 -42
- package/src/components/card/card.test.ts +19 -5
- package/src/components/consent/consent.test.ts +436 -0
- package/src/components/datepicker/datepicker.accessibility.test.ts +193 -0
- package/src/components/datepicker/datepicker.core.test.ts +322 -0
- package/src/components/datepicker/datepicker.input.test.ts +268 -0
- package/src/components/datepicker/datepicker.selection.test.ts +286 -0
- package/src/components/datepicker/datepicker.ts +121 -19
- package/src/components/datepicker/datepicker.validation.test.ts +176 -0
- package/src/components/heading/heading.test.ts +458 -0
- package/src/components/heading/heading.ts +3 -0
- package/src/components/helptext/helptext.test.ts +474 -0
- package/dist/calendar-BZe2D4Sr.cjs +0 -108
- package/dist/datepicker-B9rhz_AF.cjs +0 -154
- package/dist/heading-BRE_iFtR.cjs +0 -1
|
@@ -0,0 +1,436 @@
|
|
|
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 './consent'
|
|
8
|
+
import { PktConsent } from './consent'
|
|
9
|
+
|
|
10
|
+
const waitForCustomElements = async () => {
|
|
11
|
+
await customElements.whenDefined('pkt-consent')
|
|
12
|
+
await customElements.whenDefined('pkt-button')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Helper function to create consent markup
|
|
16
|
+
const createConsent = async (consentProps = '') => {
|
|
17
|
+
const container = document.createElement('div')
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<pkt-consent ${consentProps}></pkt-consent>
|
|
20
|
+
`
|
|
21
|
+
document.body.appendChild(container)
|
|
22
|
+
await waitForCustomElements()
|
|
23
|
+
return container
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Cleanup after each test
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
document.body.innerHTML = ''
|
|
29
|
+
// Clean up any injected scripts
|
|
30
|
+
const existingScript = document.querySelector('#oslo-consent-script')
|
|
31
|
+
if (existingScript) {
|
|
32
|
+
existingScript.remove()
|
|
33
|
+
}
|
|
34
|
+
const existingStyles = document.querySelector('#oslo-consent-styles')
|
|
35
|
+
if (existingStyles) {
|
|
36
|
+
existingStyles.remove()
|
|
37
|
+
}
|
|
38
|
+
// Clean up global variables
|
|
39
|
+
delete window.cookieBanner_googleAnalyticsId
|
|
40
|
+
delete window.cookieBanner_hotjarId
|
|
41
|
+
delete window.cookieBanner_devMode
|
|
42
|
+
delete window.cookieBanner_cookieDomain
|
|
43
|
+
delete window.cookieBanner_cookieSecure
|
|
44
|
+
delete window.cookieBanner_cookieExpiryDays
|
|
45
|
+
delete window.cookieBanner
|
|
46
|
+
delete window.__cookieEvents
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('PktConsent', () => {
|
|
50
|
+
describe('Rendering and basic functionality', () => {
|
|
51
|
+
test('renders without errors', async () => {
|
|
52
|
+
const container = await createConsent()
|
|
53
|
+
|
|
54
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
55
|
+
expect(consent).toBeInTheDocument()
|
|
56
|
+
|
|
57
|
+
await consent.updateComplete
|
|
58
|
+
expect(consent).toBeTruthy()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('renders with default trigger type as button', async () => {
|
|
62
|
+
const container = await createConsent()
|
|
63
|
+
|
|
64
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
65
|
+
await consent.updateComplete
|
|
66
|
+
|
|
67
|
+
expect(consent.triggerType).toBe('button')
|
|
68
|
+
|
|
69
|
+
const button = consent.querySelector('pkt-button')
|
|
70
|
+
expect(button).toBeInTheDocument()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('renders with custom trigger text', async () => {
|
|
74
|
+
const customText = 'Custom consent settings'
|
|
75
|
+
const container = await createConsent(`triggerText="${customText}"`)
|
|
76
|
+
|
|
77
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
78
|
+
await consent.updateComplete
|
|
79
|
+
|
|
80
|
+
expect(consent.triggerText).toBe(customText)
|
|
81
|
+
const button = consent.querySelector('pkt-button')
|
|
82
|
+
expect(button?.textContent?.trim()).toBe(customText)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('Properties and attributes', () => {
|
|
87
|
+
test('applies default properties correctly', async () => {
|
|
88
|
+
const container = await createConsent()
|
|
89
|
+
|
|
90
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
91
|
+
await consent.updateComplete
|
|
92
|
+
|
|
93
|
+
expect(consent.devMode).toBe(false)
|
|
94
|
+
expect(consent.triggerType).toBe('button')
|
|
95
|
+
expect(consent.i18nLanguage).toBe('nb')
|
|
96
|
+
expect(consent.hotjarId).toBe(null)
|
|
97
|
+
expect(consent.googleAnalyticsId).toBe(null)
|
|
98
|
+
expect(consent.cookieDomain).toBe(null)
|
|
99
|
+
expect(consent.cookieSecure).toBe(null)
|
|
100
|
+
expect(consent.cookieExpiryDays).toBe(null)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('sets dev mode correctly', async () => {
|
|
104
|
+
const container = await createConsent('devMode')
|
|
105
|
+
|
|
106
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
107
|
+
await consent.updateComplete
|
|
108
|
+
|
|
109
|
+
expect(consent.devMode).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('sets analytics IDs correctly', async () => {
|
|
113
|
+
const googleId = 'GA-123456789'
|
|
114
|
+
const hotjarId = 'HJ-987654321'
|
|
115
|
+
const container = await createConsent(
|
|
116
|
+
`googleAnalyticsId="${googleId}" hotjarId="${hotjarId}"`,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
120
|
+
await consent.updateComplete
|
|
121
|
+
|
|
122
|
+
expect(consent.googleAnalyticsId).toBe(googleId)
|
|
123
|
+
expect(consent.hotjarId).toBe(hotjarId)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('sets cookie configuration correctly', async () => {
|
|
127
|
+
const domain = '.example.com'
|
|
128
|
+
const secure = 'true'
|
|
129
|
+
const expiryDays = '365'
|
|
130
|
+
const container = await createConsent(
|
|
131
|
+
`cookieDomain="${domain}" cookieSecure="${secure}" cookieExpiryDays="${expiryDays}"`,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
135
|
+
await consent.updateComplete
|
|
136
|
+
|
|
137
|
+
expect(consent.cookieDomain).toBe(domain)
|
|
138
|
+
expect(consent.cookieSecure).toBe(secure)
|
|
139
|
+
expect(consent.cookieExpiryDays).toBe(expiryDays)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('sets language correctly', async () => {
|
|
143
|
+
const container = await createConsent('i18nLanguage="en"')
|
|
144
|
+
|
|
145
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
146
|
+
await consent.updateComplete
|
|
147
|
+
|
|
148
|
+
expect(consent.i18nLanguage).toBe('en')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('Trigger types', () => {
|
|
153
|
+
test('renders as button trigger type', async () => {
|
|
154
|
+
const container = await createConsent('triggerType="button"')
|
|
155
|
+
|
|
156
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
157
|
+
await consent.updateComplete
|
|
158
|
+
|
|
159
|
+
expect(consent.triggerType).toBe('button')
|
|
160
|
+
const button = consent.querySelector('pkt-button')
|
|
161
|
+
expect(button).toBeInTheDocument()
|
|
162
|
+
expect(button?.getAttribute('skin')).toBe(null) // Default button
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('renders as link trigger type', async () => {
|
|
166
|
+
const container = await createConsent('triggerType="link"')
|
|
167
|
+
|
|
168
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
169
|
+
await consent.updateComplete
|
|
170
|
+
|
|
171
|
+
expect(consent.triggerType).toBe('link')
|
|
172
|
+
const link = consent.querySelector('a.pkt-link')
|
|
173
|
+
expect(link).toBeInTheDocument()
|
|
174
|
+
expect(link?.getAttribute('href')).toBe('#')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('renders as footer link trigger type', async () => {
|
|
178
|
+
const container = await createConsent('triggerType="footerlink"')
|
|
179
|
+
|
|
180
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
181
|
+
await consent.updateComplete
|
|
182
|
+
|
|
183
|
+
expect(consent.triggerType).toBe('footerlink')
|
|
184
|
+
const footerLink = consent.querySelector('a.pkt-footer__link')
|
|
185
|
+
expect(footerLink).toBeInTheDocument()
|
|
186
|
+
|
|
187
|
+
const icon = footerLink?.querySelector('pkt-icon')
|
|
188
|
+
expect(icon).toBeInTheDocument()
|
|
189
|
+
expect(icon?.getAttribute('name')).toBe('chevron-right')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test('renders as icon trigger type', async () => {
|
|
193
|
+
const container = await createConsent('triggerType="icon"')
|
|
194
|
+
|
|
195
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
196
|
+
await consent.updateComplete
|
|
197
|
+
|
|
198
|
+
expect(consent.triggerType).toBe('icon')
|
|
199
|
+
const button = consent.querySelector('pkt-button')
|
|
200
|
+
expect(button).toBeInTheDocument()
|
|
201
|
+
expect(button?.getAttribute('skin')).toBe('tertiary')
|
|
202
|
+
expect(button?.getAttribute('variant')).toBe('icon-only')
|
|
203
|
+
expect(button?.getAttribute('iconName')).toBe('cookie')
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
describe('Event handling', () => {
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
// Mock the entire window.cookieBanner object
|
|
210
|
+
Object.defineProperty(window, 'cookieBanner', {
|
|
211
|
+
value: {
|
|
212
|
+
cookieConsent: {
|
|
213
|
+
validateConsentCookie: jest.fn().mockResolvedValue(true),
|
|
214
|
+
getConsentCookie: jest.fn().mockReturnValue('mock-cookie'),
|
|
215
|
+
},
|
|
216
|
+
openCookieModal: jest.fn(),
|
|
217
|
+
},
|
|
218
|
+
writable: true,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Mock window.__cookieEvents
|
|
222
|
+
Object.defineProperty(window, '__cookieEvents', {
|
|
223
|
+
value: {
|
|
224
|
+
on: jest.fn(),
|
|
225
|
+
off: jest.fn(),
|
|
226
|
+
},
|
|
227
|
+
writable: true,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Use fake timers to control setTimeout
|
|
231
|
+
jest.useFakeTimers()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
afterEach(() => {
|
|
235
|
+
// Clean up mocks and timers
|
|
236
|
+
jest.useRealTimers()
|
|
237
|
+
delete (window as any).cookieBanner
|
|
238
|
+
delete (window as any).__cookieEvents
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('handles click event on button trigger', async () => {
|
|
242
|
+
const container = await createConsent()
|
|
243
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
244
|
+
await consent.updateComplete
|
|
245
|
+
|
|
246
|
+
const button = consent.querySelector('pkt-button')
|
|
247
|
+
expect(button).toBeInTheDocument()
|
|
248
|
+
|
|
249
|
+
fireEvent.click(button!)
|
|
250
|
+
|
|
251
|
+
// Fast-forward time to trigger setTimeout
|
|
252
|
+
jest.runAllTimers()
|
|
253
|
+
|
|
254
|
+
expect(window.cookieBanner.openCookieModal).toHaveBeenCalled()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('handles click event on link trigger', async () => {
|
|
258
|
+
const container = await createConsent('triggerType="link"')
|
|
259
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
260
|
+
await consent.updateComplete
|
|
261
|
+
|
|
262
|
+
const link = consent.querySelector('a.pkt-link')
|
|
263
|
+
expect(link).toBeInTheDocument()
|
|
264
|
+
|
|
265
|
+
fireEvent.click(link!)
|
|
266
|
+
|
|
267
|
+
// Fast-forward time to trigger setTimeout
|
|
268
|
+
jest.runAllTimers()
|
|
269
|
+
|
|
270
|
+
expect(window.cookieBanner.openCookieModal).toHaveBeenCalled()
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('dispatches toggle-consent event', async () => {
|
|
274
|
+
const container = await createConsent()
|
|
275
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
276
|
+
await consent.updateComplete
|
|
277
|
+
|
|
278
|
+
let eventFired = false
|
|
279
|
+
let eventDetail: any = null
|
|
280
|
+
|
|
281
|
+
consent.addEventListener('toggle-consent', (e: Event) => {
|
|
282
|
+
eventFired = true
|
|
283
|
+
eventDetail = (e as CustomEvent).detail
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// Mock consent data
|
|
287
|
+
const mockConsent = {
|
|
288
|
+
value: JSON.stringify({
|
|
289
|
+
items: [
|
|
290
|
+
{ name: 'analytics', consent: true },
|
|
291
|
+
{ name: 'marketing', consent: false },
|
|
292
|
+
],
|
|
293
|
+
}),
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
consent.emitCookieConsents(mockConsent)
|
|
297
|
+
|
|
298
|
+
expect(eventFired).toBe(true)
|
|
299
|
+
expect(eventDetail).toEqual({
|
|
300
|
+
analytics: true,
|
|
301
|
+
marketing: false,
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
describe('Utility methods', () => {
|
|
307
|
+
test('returnJsonOrObject handles JSON strings', async () => {
|
|
308
|
+
const container = await createConsent()
|
|
309
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
310
|
+
|
|
311
|
+
const jsonString = '{"test": "value"}'
|
|
312
|
+
const result = consent.returnJsonOrObject(jsonString)
|
|
313
|
+
|
|
314
|
+
expect(result).toEqual({ test: 'value' })
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
test('returnJsonOrObject handles non-JSON objects', async () => {
|
|
318
|
+
const container = await createConsent()
|
|
319
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
320
|
+
|
|
321
|
+
const nonJsonObject = { test: 'value' }
|
|
322
|
+
const result = consent.returnJsonOrObject(nonJsonObject)
|
|
323
|
+
|
|
324
|
+
expect(result).toEqual(nonJsonObject)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
test('returnJsonOrObject handles invalid JSON gracefully', async () => {
|
|
328
|
+
const container = await createConsent()
|
|
329
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
330
|
+
|
|
331
|
+
const invalidJson = 'invalid json string'
|
|
332
|
+
const result = consent.returnJsonOrObject(invalidJson)
|
|
333
|
+
|
|
334
|
+
expect(result).toBe(invalidJson)
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
describe('Accessibility', () => {
|
|
339
|
+
test('button trigger is accessible', async () => {
|
|
340
|
+
const container = await createConsent('triggerText="Cookie settings"')
|
|
341
|
+
|
|
342
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
343
|
+
await consent.updateComplete
|
|
344
|
+
|
|
345
|
+
const results = await axe(container)
|
|
346
|
+
expect(results).toHaveNoViolations()
|
|
347
|
+
|
|
348
|
+
const button = consent.querySelector('pkt-button')
|
|
349
|
+
expect(button).toBeInTheDocument()
|
|
350
|
+
expect(button?.textContent?.trim()).toBe('Cookie settings')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('link trigger is accessible', async () => {
|
|
354
|
+
const container = await createConsent('triggerType="link" triggerText="Cookie settings"')
|
|
355
|
+
|
|
356
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
357
|
+
await consent.updateComplete
|
|
358
|
+
|
|
359
|
+
const results = await axe(container)
|
|
360
|
+
expect(results).toHaveNoViolations()
|
|
361
|
+
|
|
362
|
+
const link = consent.querySelector('a')
|
|
363
|
+
expect(link).toBeInTheDocument()
|
|
364
|
+
expect(link?.textContent?.trim()).toBe('Cookie settings')
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('footer link trigger is accessible', async () => {
|
|
368
|
+
const container = await createConsent(
|
|
369
|
+
'triggerType="footerlink" triggerText="Cookie settings"',
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
373
|
+
await consent.updateComplete
|
|
374
|
+
|
|
375
|
+
const results = await axe(container)
|
|
376
|
+
expect(results).toHaveNoViolations()
|
|
377
|
+
|
|
378
|
+
const link = consent.querySelector('a.pkt-footer__link')
|
|
379
|
+
expect(link).toBeInTheDocument()
|
|
380
|
+
expect(link?.textContent?.trim()).toContain('Cookie settings')
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test('icon trigger is accessible', async () => {
|
|
384
|
+
const container = await createConsent('triggerType="icon" triggerText="Cookie settings"')
|
|
385
|
+
|
|
386
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
387
|
+
await consent.updateComplete
|
|
388
|
+
|
|
389
|
+
const results = await axe(container)
|
|
390
|
+
expect(results).toHaveNoViolations()
|
|
391
|
+
|
|
392
|
+
const button = consent.querySelector('pkt-button')
|
|
393
|
+
expect(button).toBeInTheDocument()
|
|
394
|
+
expect(button?.textContent?.trim()).toContain('Cookie settings')
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
describe('Integration and lifecycle', () => {
|
|
399
|
+
test('sets global variables on firstUpdated', async () => {
|
|
400
|
+
const googleId = 'GA-123456789'
|
|
401
|
+
const hotjarId = 'HJ-987654321'
|
|
402
|
+
const container = await createConsent(
|
|
403
|
+
`devMode googleAnalyticsId="${googleId}" hotjarId="${hotjarId}" cookieDomain=".test.com"`,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
407
|
+
await consent.updateComplete
|
|
408
|
+
|
|
409
|
+
expect(window.cookieBanner_googleAnalyticsId).toBe(googleId)
|
|
410
|
+
expect(window.cookieBanner_hotjarId).toBe(hotjarId)
|
|
411
|
+
expect(window.cookieBanner_devMode).toBe(true)
|
|
412
|
+
expect(window.cookieBanner_cookieDomain).toBe('.test.com')
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
test('cleans up event listeners on disconnect', async () => {
|
|
416
|
+
const container = await createConsent()
|
|
417
|
+
const consent = container.querySelector('pkt-consent') as PktConsent
|
|
418
|
+
await consent.updateComplete
|
|
419
|
+
|
|
420
|
+
// Mock the event system
|
|
421
|
+
window.__cookieEvents = {
|
|
422
|
+
on: jest.fn(),
|
|
423
|
+
off: jest.fn(),
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Set up a handler
|
|
427
|
+
const mockHandler = jest.fn()
|
|
428
|
+
consent['_cookieEventHandler'] = mockHandler
|
|
429
|
+
|
|
430
|
+
// Disconnect the component
|
|
431
|
+
consent.disconnectedCallback()
|
|
432
|
+
|
|
433
|
+
expect(window.__cookieEvents.off).toHaveBeenCalledWith('CookieManager.setCookie', mockHandler)
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
})
|
|
@@ -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
|
+
})
|