@oslokommune/punkt-elements 13.5.0 → 13.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,474 @@
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 './helptext'
8
+ import { PktHelptext } from './helptext'
9
+
10
+ const waitForCustomElements = async () => {
11
+ await customElements.whenDefined('pkt-helptext')
12
+ await customElements.whenDefined('pkt-icon')
13
+ }
14
+
15
+ // Helper function to create helptext markup
16
+ const createHelptext = async (helptextProps = '', content = '') => {
17
+ const container = document.createElement('div')
18
+ container.innerHTML = `
19
+ <pkt-helptext ${helptextProps}>
20
+ ${content}
21
+ </pkt-helptext>
22
+ `
23
+ document.body.appendChild(container)
24
+ await waitForCustomElements()
25
+ return container
26
+ }
27
+
28
+ // Cleanup after each test
29
+ afterEach(() => {
30
+ document.body.innerHTML = ''
31
+ })
32
+
33
+ describe('PktHelptext', () => {
34
+ describe('Rendering and basic functionality', () => {
35
+ test('renders without errors', async () => {
36
+ const container = await createHelptext()
37
+
38
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
39
+ expect(helptext).toBeInTheDocument()
40
+
41
+ await helptext.updateComplete
42
+ expect(helptext).toBeTruthy()
43
+ })
44
+
45
+ test('renders with basic helptext', async () => {
46
+ const helptextContent = 'This is helpful information'
47
+ const container = await createHelptext(`helptext="${helptextContent}"`)
48
+
49
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
50
+ await helptext.updateComplete
51
+
52
+ expect(helptext.helptext).toBe(helptextContent)
53
+
54
+ const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
55
+ expect(helptextContainer).toBeInTheDocument()
56
+ expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
57
+
58
+ const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
59
+ expect(helptextDiv).toBeInTheDocument()
60
+ expect(helptextDiv?.textContent?.trim()).toContain(helptextContent)
61
+ })
62
+
63
+ test('renders with slot content', async () => {
64
+ const slotContent = '<p>Slot helptext content</p>'
65
+ const container = await createHelptext('', slotContent)
66
+
67
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
68
+ await helptext.updateComplete
69
+
70
+ const contentSlot = helptext.querySelector('.pkt-contents')
71
+ expect(contentSlot).toBeInTheDocument()
72
+ expect(helptext.textContent).toContain('Slot helptext content')
73
+ })
74
+
75
+ test('renders with dropdown helptext', async () => {
76
+ const dropdownContent = 'This is expandable helptext content'
77
+ const container = await createHelptext(`helptextDropdown="${dropdownContent}"`)
78
+
79
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
80
+ await helptext.updateComplete
81
+
82
+ expect(helptext.helptextDropdown).toBe(dropdownContent)
83
+
84
+ const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
85
+ expect(expandableContainer).toBeInTheDocument()
86
+
87
+ const button = expandableContainer?.querySelector('button')
88
+ expect(button).toBeInTheDocument()
89
+ expect(button).toHaveClass('pkt-link')
90
+
91
+ const icon = button?.querySelector('pkt-icon')
92
+ expect(icon).toBeInTheDocument()
93
+ })
94
+ })
95
+
96
+ describe('Properties and attributes', () => {
97
+ test('applies default properties correctly', async () => {
98
+ const container = await createHelptext()
99
+
100
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
101
+ await helptext.updateComplete
102
+
103
+ expect(helptext.forId).toBeTruthy() // Should have a generated ID
104
+ expect(helptext.helptext).toBe('')
105
+ expect(helptext.helptextDropdown).toBe('')
106
+ expect(helptext.helptextDropdownButton).toBeTruthy() // Should have default from specs
107
+ expect(helptext.isHelpTextOpen).toBe(false)
108
+ })
109
+
110
+ test('sets forId correctly', async () => {
111
+ const customId = 'custom-helptext-id'
112
+ const container = await createHelptext(`forId="${customId}"`)
113
+
114
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
115
+ await helptext.updateComplete
116
+
117
+ expect(helptext.forId).toBe(customId)
118
+
119
+ const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
120
+ expect(helptextDiv?.id).toBe(`${customId}-helptext`)
121
+ })
122
+
123
+ test('sets helptext content correctly', async () => {
124
+ const helptextContent = 'Custom helptext message'
125
+ const container = await createHelptext(`helptext="${helptextContent}"`)
126
+
127
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
128
+ await helptext.updateComplete
129
+
130
+ expect(helptext.helptext).toBe(helptextContent)
131
+ expect(helptext.textContent).toContain(helptextContent)
132
+ })
133
+
134
+ test('sets dropdown helptext correctly', async () => {
135
+ const dropdownContent = 'Dropdown helptext content'
136
+ const container = await createHelptext(`helptextDropdown="${dropdownContent}"`)
137
+
138
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
139
+ await helptext.updateComplete
140
+
141
+ expect(helptext.helptextDropdown).toBe(dropdownContent)
142
+ })
143
+
144
+ test('sets dropdown button text correctly', async () => {
145
+ const buttonText = 'Custom button text'
146
+ const container = await createHelptext(`helptextDropdownButton="${buttonText}"`)
147
+
148
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
149
+ await helptext.updateComplete
150
+
151
+ expect(helptext.helptextDropdownButton).toBe(buttonText)
152
+ })
153
+ })
154
+
155
+ describe('Dropdown functionality', () => {
156
+ test('does not render dropdown when no dropdown content', async () => {
157
+ const container = await createHelptext('helptext="Regular helptext"')
158
+
159
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
160
+ await helptext.updateComplete
161
+
162
+ const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
163
+ expect(expandableContainer).not.toBeInTheDocument()
164
+ })
165
+
166
+ test('renders dropdown when dropdown content is provided', async () => {
167
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
168
+
169
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
170
+ await helptext.updateComplete
171
+
172
+ const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
173
+ expect(expandableContainer).toBeInTheDocument()
174
+
175
+ const button = expandableContainer?.querySelector('button')
176
+ expect(button).toBeInTheDocument()
177
+
178
+ const expandableContent = expandableContainer?.querySelector(
179
+ '.pkt-inputwrapper__helptext-expandable-closed',
180
+ )
181
+ expect(expandableContent).toBeInTheDocument()
182
+ })
183
+
184
+ test('toggles dropdown state on button click', async () => {
185
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
186
+
187
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
188
+ await helptext.updateComplete
189
+
190
+ expect(helptext.isHelpTextOpen).toBe(false)
191
+
192
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
193
+ expect(button).toBeInTheDocument()
194
+
195
+ // Click to open
196
+ fireEvent.click(button!)
197
+ await helptext.updateComplete
198
+
199
+ expect(helptext.isHelpTextOpen).toBe(true)
200
+
201
+ const openContent = helptext.querySelector('.pkt-inputwrapper__helptext-expandable-open')
202
+ expect(openContent).toBeInTheDocument()
203
+
204
+ const closedContent = helptext.querySelector('.pkt-inputwrapper__helptext-expandable-closed')
205
+ expect(closedContent).not.toBeInTheDocument()
206
+
207
+ // Click to close
208
+ fireEvent.click(button!)
209
+ await helptext.updateComplete
210
+
211
+ expect(helptext.isHelpTextOpen).toBe(false)
212
+ })
213
+
214
+ test('changes icon when dropdown is toggled', async () => {
215
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
216
+
217
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
218
+ await helptext.updateComplete
219
+
220
+ const icon = helptext.querySelector('pkt-icon')
221
+ expect(icon?.getAttribute('name')).toBe('chevron-thin-down')
222
+
223
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
224
+ fireEvent.click(button!)
225
+ await helptext.updateComplete
226
+
227
+ expect(icon?.getAttribute('name')).toBe('chevron-thin-up')
228
+ })
229
+
230
+ test('dispatches toggleHelpText event on dropdown toggle', async () => {
231
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
232
+
233
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
234
+ await helptext.updateComplete
235
+
236
+ let eventFired = false
237
+ let eventDetail: any = null
238
+
239
+ helptext.addEventListener('toggleHelpText', (e: Event) => {
240
+ eventFired = true
241
+ eventDetail = (e as CustomEvent).detail
242
+ })
243
+
244
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
245
+ fireEvent.click(button!)
246
+
247
+ expect(eventFired).toBe(true)
248
+ expect(eventDetail).toEqual({ isOpen: true })
249
+ })
250
+ })
251
+
252
+ describe('CSS classes and styling', () => {
253
+ test('applies correct classes when no content', async () => {
254
+ const container = await createHelptext()
255
+
256
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
257
+ await helptext.updateComplete
258
+
259
+ const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
260
+ expect(helptextContainer).toBeInTheDocument()
261
+ expect(helptextContainer).not.toHaveClass('pkt-inputwrapper__has-helptext')
262
+ })
263
+
264
+ test('applies correct classes when helptext is provided', async () => {
265
+ const container = await createHelptext('helptext="Some helptext"')
266
+
267
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
268
+ await helptext.updateComplete
269
+
270
+ const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
271
+ expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
272
+ })
273
+
274
+ test('applies correct classes when dropdown is provided', async () => {
275
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
276
+
277
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
278
+ await helptext.updateComplete
279
+
280
+ const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
281
+ expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
282
+ })
283
+
284
+ test('applies correct classes for closed dropdown', async () => {
285
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
286
+
287
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
288
+ await helptext.updateComplete
289
+
290
+ const dropdownContent = helptext.querySelector(
291
+ '.pkt-inputwrapper__helptext-expandable .pkt-inputwrapper__helptext',
292
+ )
293
+ expect(dropdownContent).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
294
+ expect(dropdownContent).not.toHaveClass('pkt-inputwrapper__helptext-expandable-open')
295
+ })
296
+
297
+ test('applies correct classes for open dropdown', async () => {
298
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
299
+
300
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
301
+ await helptext.updateComplete
302
+
303
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
304
+ fireEvent.click(button!)
305
+ await helptext.updateComplete
306
+
307
+ const dropdownContent = helptext.querySelector(
308
+ '.pkt-inputwrapper__helptext-expandable .pkt-inputwrapper__helptext',
309
+ )
310
+ expect(dropdownContent).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
311
+ expect(dropdownContent).not.toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
312
+ })
313
+ })
314
+
315
+ describe('Content rendering', () => {
316
+ test('renders HTML content safely in helptext', async () => {
317
+ const htmlContent = '<strong>Important</strong> information'
318
+ const container = await createHelptext(`helptext="${htmlContent}"`)
319
+
320
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
321
+ await helptext.updateComplete
322
+
323
+ const strong = helptext.querySelector('strong')
324
+ expect(strong).toBeInTheDocument()
325
+ expect(strong?.textContent).toBe('Important')
326
+ })
327
+
328
+ test('renders HTML content safely in dropdown', async () => {
329
+ const htmlContent = '<em>Emphasized</em> dropdown content'
330
+ const container = await createHelptext(`helptextDropdown="${htmlContent}"`)
331
+
332
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
333
+ await helptext.updateComplete
334
+
335
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
336
+ fireEvent.click(button!)
337
+ await helptext.updateComplete
338
+
339
+ const em = helptext.querySelector('em')
340
+ expect(em).toBeInTheDocument()
341
+ expect(em?.textContent).toBe('Emphasized')
342
+ })
343
+
344
+ test('renders both regular and dropdown content simultaneously', async () => {
345
+ const regularContent = 'Regular helptext'
346
+ const dropdownContent = 'Dropdown helptext'
347
+ const container = await createHelptext(
348
+ `helptext="${regularContent}" helptextDropdown="${dropdownContent}"`,
349
+ )
350
+
351
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
352
+ await helptext.updateComplete
353
+
354
+ expect(helptext.textContent).toContain(regularContent)
355
+
356
+ const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
357
+ fireEvent.click(button!)
358
+ await helptext.updateComplete
359
+
360
+ expect(helptext.textContent).toContain(dropdownContent)
361
+ })
362
+ })
363
+
364
+ describe('Slot management', () => {
365
+ test('updates slot state when slots are filled', async () => {
366
+ const container = await createHelptext('', '<span>Slotted content</span>')
367
+
368
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
369
+ await helptext.updateComplete
370
+
371
+ // Simulate slot controller updating filled slots
372
+ const filledSlots = new Set(['default'])
373
+ helptext.updateSlots(filledSlots)
374
+ await helptext.updateComplete
375
+
376
+ expect(helptext.filledSlots.size).toBe(1)
377
+ expect(helptext.filledSlots.has('default')).toBe(true)
378
+ })
379
+
380
+ test('applies has-helptext class when slots are filled', async () => {
381
+ const container = await createHelptext('', '<span>Slotted content</span>')
382
+
383
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
384
+
385
+ // Simulate slot controller updating filled slots
386
+ const filledSlots = new Set(['default'])
387
+ helptext.updateSlots(filledSlots)
388
+ await helptext.updateComplete
389
+
390
+ const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
391
+ expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
392
+ })
393
+ })
394
+
395
+ describe('Accessibility', () => {
396
+ test('basic helptext is accessible', async () => {
397
+ const container = await createHelptext('helptext="Accessible helptext" forId="test-input"')
398
+
399
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
400
+ await helptext.updateComplete
401
+
402
+ const results = await axe(container)
403
+ expect(results).toHaveNoViolations()
404
+
405
+ const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
406
+ expect(helptextDiv?.id).toBe('test-input-helptext')
407
+ })
408
+
409
+ test('dropdown helptext is accessible', async () => {
410
+ const container = await createHelptext(
411
+ 'helptextDropdown="Dropdown content" helptextDropdownButton="Show more info"',
412
+ )
413
+
414
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
415
+ await helptext.updateComplete
416
+
417
+ const results = await axe(container)
418
+ expect(results).toHaveNoViolations()
419
+
420
+ const button = helptext.querySelector('button')
421
+ expect(button).toBeInTheDocument()
422
+ expect(button?.type).toBe('button')
423
+ expect(button?.textContent?.trim()).toContain('Show more info')
424
+ })
425
+
426
+ test('dropdown button has proper aria state', async () => {
427
+ const container = await createHelptext('helptextDropdown="Dropdown content"')
428
+
429
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
430
+ await helptext.updateComplete
431
+
432
+ const button = helptext.querySelector('button')
433
+ expect(button).toBeInTheDocument()
434
+
435
+ // Button should be focusable and have proper role
436
+ expect(button?.tagName).toBe('BUTTON')
437
+ expect(button?.type).toBe('button')
438
+ })
439
+ })
440
+
441
+ describe('Integration', () => {
442
+ test('works with input wrapper context', async () => {
443
+ const container = await createHelptext('forId="input-123" helptext="Field help information"')
444
+
445
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
446
+ await helptext.updateComplete
447
+
448
+ // The ID should match what an input would use for aria-describedby
449
+ const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
450
+ expect(helptextDiv?.id).toBe('input-123-helptext')
451
+ })
452
+
453
+ test('manages multiple content sources correctly', async () => {
454
+ const container = await createHelptext(
455
+ 'helptext="Property text" helptextDropdown="Dropdown text"',
456
+ '<span>Slot text</span>',
457
+ )
458
+
459
+ const helptext = container.querySelector('pkt-helptext') as PktHelptext
460
+ await helptext.updateComplete
461
+
462
+ // All content should be present but in correct locations
463
+ expect(helptext.textContent).toContain('Property text')
464
+ expect(helptext.textContent).toContain('Slot text')
465
+
466
+ // Dropdown content should be present but hidden initially
467
+ const dropdownContent = helptext.querySelector(
468
+ '.pkt-inputwrapper__helptext-expandable-closed',
469
+ )
470
+ expect(dropdownContent).toBeInTheDocument()
471
+ expect(dropdownContent?.textContent?.trim()).toContain('Dropdown text')
472
+ })
473
+ })
474
+ })