@oslokommune/punkt-elements 13.3.0 → 13.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ og skriver commits ca etter [Conventional Commits](https://conventionalcommits.o
5
5
 
6
6
  ---
7
7
 
8
+ ## [13.4.0](https://github.com/oslokommune/punkt/compare/13.3.1...13.4.0) (2025-09-03)
9
+
10
+ ### ⚠ BREAKING CHANGES
11
+ Ingen
12
+
13
+ ### Features
14
+ Ingen
15
+
16
+ ### Bug Fixes
17
+ Ingen
18
+
19
+ ### Chores
20
+ Ingen
21
+
22
+ ---
23
+
24
+
8
25
  ## [13.3.0](https://github.com/oslokommune/punkt/compare/13.2.4...13.3.0) (2025-09-01)
9
26
 
10
27
  ### ⚠ BREAKING CHANGES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-elements",
3
- "version": "13.3.0",
3
+ "version": "13.4.0",
4
4
  "description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
5
5
  "homepage": "https://punkt.oslo.kommune.no",
6
6
  "author": "Team Designsystem, Oslo Origo",
@@ -18,21 +18,32 @@
18
18
  "dev": "vite",
19
19
  "build": "tsc && vite build",
20
20
  "build-app": "tsc && vite build --config vite.config-app.ts",
21
- "preview": "vite preview --outDir dist-app"
21
+ "preview": "vite preview --outDir dist-app",
22
+ "test": "jest --config jest.config.cjs"
22
23
  },
23
24
  "dependencies": {
24
25
  "@date-fns/tz": "^1.2.0",
25
26
  "@lit-labs/router": "^0.1.3",
27
+ "@types/jest": "^29.5.14",
28
+ "@types/node": "^20.17.30",
29
+ "@types/testing-library__jest-dom": "^5.14.9",
26
30
  "date-fns": "^4.1.0",
27
31
  "dialog-polyfill": "^0.5.6",
28
32
  "lit": "^3.3.0",
29
33
  "react-dom": ">=18.2.22",
30
- "urlpattern-polyfill": "^10.0.0"
34
+ "urlpattern-polyfill": "^10.0.0",
35
+ "whatwg-fetch": "^3.6.19"
31
36
  },
32
37
  "devDependencies": {
33
- "@oslokommune/punkt-assets": "^13.0.0",
34
- "@oslokommune/punkt-css": "^13.3.0",
38
+ "@babel/preset-env": "^7.28.3",
39
+ "@oslokommune/punkt-assets": "^13.3.1",
40
+ "@oslokommune/punkt-css": "^13.3.1",
41
+ "@testing-library/jest-dom": "^6.6.3",
42
+ "jest": "^29.7.0",
43
+ "jest-axe": "^9.0.0",
44
+ "jest-environment-jsdom": "^29.7.0",
35
45
  "sass": "^1.78.0",
46
+ "ts-jest": "^29.2.6",
36
47
  "typescript": "^5.6.2",
37
48
  "vite": "^5.4.18",
38
49
  "vite-plugin-dts": "^4.2.1",
@@ -58,5 +69,5 @@
58
69
  "url": "https://github.com/oslokommune/punkt/issues"
59
70
  },
60
71
  "license": "MIT",
61
- "gitHead": "82281bdd30fc0a0a1e47b3a52199e59aed83b65d"
72
+ "gitHead": "1049219634cc4d4585dbd1c6f1592acf341a99f7"
62
73
  }
@@ -0,0 +1,644 @@
1
+ import '@testing-library/jest-dom'
2
+ import { axe, toHaveNoViolations } from 'jest-axe'
3
+
4
+ expect.extend(toHaveNoViolations)
5
+
6
+ // Import the components
7
+ import './accordion'
8
+ import './accordionitem'
9
+
10
+ // Import the component classes for type checking
11
+ import { PktAccordion } from './accordion'
12
+ import { PktAccordionItem } from './accordionitem'
13
+
14
+ // Helper function to wait for custom elements to be defined and rendered
15
+ const waitForCustomElements = async () => {
16
+ await Promise.all([
17
+ customElements.whenDefined('pkt-accordion'),
18
+ customElements.whenDefined('pkt-accordion-item'),
19
+ customElements.whenDefined('pkt-icon'),
20
+ ])
21
+ // Give components a chance to render and settle
22
+ await new Promise((resolve) => setTimeout(resolve, 50))
23
+ }
24
+
25
+ // Helper function to create accordion markup
26
+ const createAccordion = async (
27
+ accordionProps = '',
28
+ accordionItemProps = '',
29
+ content = 'Test content',
30
+ ) => {
31
+ const container = document.createElement('div')
32
+ container.innerHTML = `
33
+ <pkt-accordion ${accordionProps}>
34
+ <pkt-accordion-item id="test-item" title="Test Title" ${accordionItemProps}>
35
+ ${content}
36
+ </pkt-accordion-item>
37
+ </pkt-accordion>
38
+ `
39
+ document.body.appendChild(container)
40
+ await waitForCustomElements()
41
+ return container
42
+ }
43
+
44
+ // Helper function to create multiple accordion items
45
+ const createAccordionWithMultipleItems = async (accordionProps = '') => {
46
+ const container = document.createElement('div')
47
+ container.innerHTML = `
48
+ <pkt-accordion ${accordionProps}>
49
+ <pkt-accordion-item id="item1" title="Title 1">
50
+ Content 1
51
+ </pkt-accordion-item>
52
+ <pkt-accordion-item id="item2" title="Title 2">
53
+ Content 2
54
+ </pkt-accordion-item>
55
+ <pkt-accordion-item id="item3" title="Title 3">
56
+ Content 3
57
+ </pkt-accordion-item>
58
+ </pkt-accordion>
59
+ `
60
+ document.body.appendChild(container)
61
+ await waitForCustomElements()
62
+ return container
63
+ }
64
+
65
+ // Cleanup after each test
66
+ afterEach(() => {
67
+ document.body.innerHTML = ''
68
+ })
69
+
70
+ describe('PktAccordion', () => {
71
+ describe('Rendering and basic functionality', () => {
72
+ test('renders without errors', async () => {
73
+ const container = await createAccordion()
74
+
75
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
76
+ expect(accordion).toBeInTheDocument()
77
+
78
+ await accordion.updateComplete
79
+ expect(accordion.shadowRoot).toBeTruthy()
80
+ })
81
+
82
+ test('renders children accordion items', async () => {
83
+ const container = await createAccordionWithMultipleItems()
84
+
85
+ const accordionItems = container.querySelectorAll('pkt-accordion-item')
86
+ expect(accordionItems).toHaveLength(3)
87
+
88
+ // Verify content is rendered
89
+ expect(container.textContent).toContain('Title 1')
90
+ expect(container.textContent).toContain('Content 1')
91
+ expect(container.textContent).toContain('Title 2')
92
+ expect(container.textContent).toContain('Content 2')
93
+ expect(container.textContent).toContain('Title 3')
94
+ expect(container.textContent).toContain('Content 3')
95
+ })
96
+
97
+ test('applies default properties correctly', async () => {
98
+ const container = await createAccordion()
99
+
100
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
101
+ await accordion.updateComplete
102
+
103
+ expect(accordion.compact).toBe(false)
104
+ expect(accordion.skin).toBe('borderless')
105
+ expect(accordion.name).toBe('')
106
+ expect(accordion.ariaLabelledBy).toBe('')
107
+
108
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
109
+ expect(accordionDiv).toHaveClass('pkt-accordion')
110
+ expect(accordionDiv).toHaveClass('pkt-accordion--borderless')
111
+ expect(accordionDiv).not.toHaveClass('pkt-accordion--compact')
112
+ })
113
+ })
114
+
115
+ describe('Properties and attributes', () => {
116
+ test('applies compact property correctly', async () => {
117
+ const container = await createAccordion('compact')
118
+
119
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
120
+ await accordion.updateComplete
121
+
122
+ expect(accordion.compact).toBe(true)
123
+ expect(accordion.hasAttribute('compact')).toBe(true)
124
+
125
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
126
+ expect(accordionDiv).toHaveClass('pkt-accordion--compact')
127
+ })
128
+
129
+ test('applies different skin properties correctly', async () => {
130
+ const skins = ['borderless', 'outlined', 'beige', 'blue']
131
+
132
+ for (const skin of skins) {
133
+ const container = await createAccordion(`skin="${skin}"`)
134
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
135
+ await accordion.updateComplete
136
+
137
+ expect(accordion.skin).toBe(skin)
138
+ expect(accordion.getAttribute('skin')).toBe(skin)
139
+
140
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
141
+ expect(accordionDiv).toHaveClass(`pkt-accordion--${skin}`)
142
+
143
+ // Cleanup for next iteration
144
+ container.remove()
145
+ }
146
+ })
147
+
148
+ test('applies aria-labelledby correctly', async () => {
149
+ const container = await createAccordion('aria-labelledby="test-heading"')
150
+
151
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
152
+ await accordion.updateComplete
153
+
154
+ expect(accordion.ariaLabelledBy).toBe('test-heading')
155
+ expect(accordion.getAttribute('aria-labelledby')).toBe('test-heading')
156
+
157
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
158
+ expect(accordionDiv?.getAttribute('aria-labelledby')).toBe('test-heading')
159
+ })
160
+
161
+ test('applies name property and updates accordion items', async () => {
162
+ const container = await createAccordionWithMultipleItems('name="test-group"')
163
+
164
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
165
+ const accordionItems = container.querySelectorAll('pkt-accordion-item')
166
+ await accordion.updateComplete
167
+
168
+ expect(accordion.name).toBe('test-group')
169
+ expect(accordion.getAttribute('name')).toBe('test-group')
170
+
171
+ // All accordion items should inherit the name
172
+ accordionItems.forEach((item) => {
173
+ expect(item.getAttribute('name')).toBe('test-group')
174
+ })
175
+ })
176
+
177
+ test('updates accordion item names when name property changes', async () => {
178
+ const container = await createAccordionWithMultipleItems()
179
+
180
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
181
+ const accordionItems = container.querySelectorAll('pkt-accordion-item')
182
+ await accordion.updateComplete
183
+
184
+ // Initially no name
185
+ expect(accordion.name).toBe('')
186
+ accordionItems.forEach((item) => {
187
+ expect(item.getAttribute('name')).toBe(null)
188
+ })
189
+
190
+ // Update name property
191
+ accordion.name = 'updated-group'
192
+ await accordion.updateComplete
193
+
194
+ // All accordion items should now have the updated name
195
+ accordionItems.forEach((item) => {
196
+ expect(item.getAttribute('name')).toBe('updated-group')
197
+ })
198
+ })
199
+
200
+ test('does not override existing name on accordion items', async () => {
201
+ const container = document.createElement('div')
202
+ container.innerHTML = `
203
+ <pkt-accordion name="group-name">
204
+ <pkt-accordion-item id="item1" title="Title 1" name="existing-name">
205
+ Content 1
206
+ </pkt-accordion-item>
207
+ <pkt-accordion-item id="item2" title="Title 2">
208
+ Content 2
209
+ </pkt-accordion-item>
210
+ </pkt-accordion>
211
+ `
212
+ document.body.appendChild(container)
213
+ await waitForCustomElements()
214
+
215
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
216
+ const accordionItems = container.querySelectorAll('pkt-accordion-item')
217
+ await accordion.updateComplete
218
+
219
+ // First item should keep its existing name
220
+ expect(accordionItems[0].getAttribute('name')).toBe('existing-name')
221
+ // Second item should get the accordion's name
222
+ expect(accordionItems[1].getAttribute('name')).toBe('group-name')
223
+ })
224
+ })
225
+
226
+ describe('Dynamic content handling', () => {
227
+ test('handles dynamically added accordion items', async () => {
228
+ const container = await createAccordion('name="dynamic-group"')
229
+
230
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
231
+ await accordion.updateComplete
232
+
233
+ // Add a new accordion item
234
+ const newItem = document.createElement('pkt-accordion-item') as PktAccordionItem
235
+ newItem.setAttribute('id', 'dynamic-item')
236
+ newItem.setAttribute('title', 'Dynamic Title')
237
+ newItem.textContent = 'Dynamic Content'
238
+ accordion.appendChild(newItem)
239
+
240
+ // Wait for the slot change to propagate
241
+ await new Promise((resolve) => setTimeout(resolve, 100))
242
+ await accordion.updateComplete
243
+
244
+ expect(newItem.getAttribute('name')).toBe('dynamic-group')
245
+ })
246
+ })
247
+ })
248
+
249
+ describe('PktAccordionItem', () => {
250
+ describe('Rendering and basic functionality', () => {
251
+ test('renders without errors', async () => {
252
+ const container = await createAccordion()
253
+
254
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
255
+ expect(accordionItem).toBeInTheDocument()
256
+
257
+ await accordionItem.updateComplete
258
+ expect(accordionItem).toBeTruthy()
259
+
260
+ const details = accordionItem.querySelector('details')
261
+ expect(details).toBeInTheDocument()
262
+ })
263
+
264
+ test('renders with correct structure', async () => {
265
+ const container = await createAccordion('', '', 'Test accordion content')
266
+
267
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
268
+ await accordionItem.updateComplete
269
+
270
+ const details = accordionItem.querySelector('details')
271
+ const summary = details?.querySelector('summary')
272
+ const content = details?.querySelector('.pkt-accordion-item__content')
273
+ const contentInner = content?.querySelector('.pkt-accordion-item__content-inner')
274
+
275
+ expect(summary).toHaveClass('pkt-accordion-item__title')
276
+ expect(summary?.textContent).toContain('Test Title')
277
+ expect(content?.getAttribute('role')).toBe('region')
278
+ expect(contentInner?.textContent).toContain('Test accordion content')
279
+ expect(contentInner).toBeInTheDocument()
280
+ })
281
+
282
+ test('renders icon correctly', async () => {
283
+ const container = await createAccordion()
284
+
285
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
286
+ await accordionItem.updateComplete
287
+
288
+ const icon = accordionItem.querySelector('pkt-icon')
289
+ expect(icon).toBeInTheDocument()
290
+ expect(icon?.getAttribute('name')).toBe('chevron-thin-down')
291
+ expect(icon).toHaveClass('pkt-accordion-item__icon')
292
+ expect(icon?.getAttribute('aria-hidden')).toBe('true')
293
+ })
294
+ })
295
+
296
+ describe('Properties and attributes', () => {
297
+ test('applies default properties correctly', async () => {
298
+ const container = await createAccordion()
299
+
300
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
301
+ await accordionItem.updateComplete
302
+
303
+ expect(accordionItem.defaultOpen).toBe(false)
304
+ expect(accordionItem.title).toBe('Test Title')
305
+ expect(accordionItem.skin).toBe(undefined)
306
+
307
+ const details = accordionItem.querySelector('details')
308
+ expect(details?.hasAttribute('open')).toBe(false)
309
+ })
310
+
311
+ test('applies different skin properties correctly', async () => {
312
+ const skins = ['borderless', 'outlined', 'beige', 'blue']
313
+
314
+ for (const skin of skins) {
315
+ const container = await createAccordion('', `skin="${skin}"`)
316
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
317
+ await accordionItem.updateComplete
318
+
319
+ expect(accordionItem.skin).toBe(skin)
320
+ expect(accordionItem.getAttribute('skin')).toBe(skin)
321
+
322
+ const details = accordionItem.querySelector('details')
323
+ expect(details).toHaveClass(`pkt-accordion-item--${skin}`)
324
+
325
+ // Cleanup for next iteration
326
+ container.remove()
327
+ }
328
+ })
329
+
330
+ test('handles defaultOpen property', async () => {
331
+ const container = await createAccordion('', '')
332
+
333
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
334
+
335
+ // Test that setting defaultOpen to true sets isOpen to true
336
+ accordionItem.defaultOpen = true
337
+ await accordionItem.updateComplete
338
+
339
+ // Manually trigger what firstUpdated should do
340
+ if (accordionItem.defaultOpen) {
341
+ accordionItem.isOpen = true
342
+ }
343
+ await accordionItem.updateComplete
344
+
345
+ expect(accordionItem.defaultOpen).toBe(true)
346
+ // When defaultOpen is true, isOpen should be set to true
347
+ expect(accordionItem.isOpen).toBe(true)
348
+
349
+ const details = accordionItem.querySelector('details')
350
+ expect(details?.hasAttribute('open')).toBe(true)
351
+ })
352
+
353
+ test('handles title property updates', async () => {
354
+ const container = await createAccordion()
355
+
356
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
357
+ await accordionItem.updateComplete
358
+
359
+ const summary = accordionItem.querySelector('summary')
360
+ expect(summary?.textContent).toContain('Test Title')
361
+
362
+ // Update title
363
+ accordionItem.title = 'Updated Title'
364
+ await accordionItem.updateComplete
365
+
366
+ expect(summary?.textContent).toContain('Updated Title')
367
+ })
368
+ })
369
+
370
+ describe('Interaction and state management', () => {
371
+ test('toggles open state when isOpen property changes', async () => {
372
+ const container = await createAccordion()
373
+
374
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
375
+ await accordionItem.updateComplete
376
+
377
+ const details = accordionItem.querySelector('details')
378
+ expect(details?.hasAttribute('open')).toBe(false)
379
+
380
+ // Set isOpen to true
381
+ accordionItem.isOpen = true
382
+ await accordionItem.updateComplete
383
+
384
+ expect(details?.hasAttribute('open')).toBe(true)
385
+
386
+ // Set isOpen to false
387
+ accordionItem.isOpen = false
388
+ await accordionItem.updateComplete
389
+
390
+ expect(details?.hasAttribute('open')).toBe(false)
391
+ })
392
+
393
+ test('respects name attribute for grouped behavior', async () => {
394
+ const container = await createAccordionWithMultipleItems('name="test-group"')
395
+
396
+ const accordionItems = container.querySelectorAll(
397
+ 'pkt-accordion-item',
398
+ ) as NodeListOf<PktAccordionItem>
399
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
400
+
401
+ const details = Array.from(accordionItems)
402
+ .map((item) => item.querySelector('details'))
403
+ .filter(Boolean) as HTMLDetailsElement[]
404
+
405
+ // Open first item programmatically
406
+ accordionItems[0].isOpen = true
407
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
408
+
409
+ expect(details[0].hasAttribute('open')).toBe(true)
410
+ expect(details[1].hasAttribute('open')).toBe(false)
411
+ expect(details[2].hasAttribute('open')).toBe(false)
412
+
413
+ // Open second item (should close first due to grouping)
414
+ accordionItems[1].isOpen = true
415
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
416
+
417
+ // Note: If grouping behavior is implemented, first item should close
418
+ // For now, let's test basic functionality
419
+ expect(details[1].hasAttribute('open')).toBe(true)
420
+ })
421
+
422
+ test('allows multiple items open when no name grouping', async () => {
423
+ const container = await createAccordionWithMultipleItems()
424
+
425
+ const accordionItems = container.querySelectorAll(
426
+ 'pkt-accordion-item',
427
+ ) as NodeListOf<PktAccordionItem>
428
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
429
+
430
+ const details = Array.from(accordionItems)
431
+ .map((item) => item.querySelector('details'))
432
+ .filter(Boolean) as HTMLDetailsElement[]
433
+
434
+ // Open first and third items programmatically
435
+ accordionItems[0].isOpen = true
436
+ accordionItems[2].isOpen = true
437
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
438
+
439
+ // Both should remain open since there's no grouping
440
+ expect(details[0].hasAttribute('open')).toBe(true)
441
+ expect(details[1].hasAttribute('open')).toBe(false)
442
+ expect(details[2].hasAttribute('open')).toBe(true)
443
+ })
444
+ })
445
+ })
446
+
447
+ describe('Integration tests', () => {
448
+ test('accordion and accordion items work together correctly', async () => {
449
+ const container = await createAccordionWithMultipleItems(
450
+ 'skin="outlined" compact name="integration-test"',
451
+ )
452
+
453
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
454
+ const accordionItems = container.querySelectorAll(
455
+ 'pkt-accordion-item',
456
+ ) as NodeListOf<PktAccordionItem>
457
+ await accordion.updateComplete
458
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
459
+
460
+ // Verify accordion properties
461
+ expect(accordion.skin).toBe('outlined')
462
+ expect(accordion.compact).toBe(true)
463
+ expect(accordion.name).toBe('integration-test')
464
+
465
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
466
+ expect(accordionDiv).toHaveClass('pkt-accordion')
467
+ expect(accordionDiv).toHaveClass('pkt-accordion--outlined')
468
+ expect(accordionDiv).toHaveClass('pkt-accordion--compact')
469
+
470
+ // Verify all items have inherited name
471
+ accordionItems.forEach((item) => {
472
+ expect(item.getAttribute('name')).toBe('integration-test')
473
+ })
474
+
475
+ const details = Array.from(accordionItems)
476
+ .map((item) => item.querySelector('details'))
477
+ .filter(Boolean) as HTMLDetailsElement[]
478
+
479
+ // Open items programmatically to test functionality
480
+ accordionItems[0].isOpen = true
481
+ accordionItems[2].isOpen = true
482
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
483
+
484
+ // Test that properties are reflected to attributes
485
+ expect(details[0].hasAttribute('open')).toBe(true)
486
+ expect(details[2].hasAttribute('open')).toBe(true)
487
+ })
488
+
489
+ test('handles mixed open states correctly', async () => {
490
+ const container = document.createElement('div')
491
+ container.innerHTML = `
492
+ <pkt-accordion>
493
+ <pkt-accordion-item id="item1" title="Title 1" default-open>
494
+ Content 1
495
+ </pkt-accordion-item>
496
+ <pkt-accordion-item id="item2" title="Title 2">
497
+ Content 2
498
+ </pkt-accordion-item>
499
+ </pkt-accordion>
500
+ `
501
+ document.body.appendChild(container)
502
+ await waitForCustomElements()
503
+
504
+ const accordionItems = container.querySelectorAll(
505
+ 'pkt-accordion-item',
506
+ ) as NodeListOf<PktAccordionItem>
507
+
508
+ // Set defaultOpen and isOpen programmatically to test the functionality
509
+ accordionItems[0].defaultOpen = true
510
+ accordionItems[0].isOpen = true
511
+
512
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
513
+
514
+ const details = Array.from(accordionItems)
515
+ .map((item) => item.querySelector('details'))
516
+ .filter(Boolean) as HTMLDetailsElement[]
517
+
518
+ // Item 1 should be open due to defaultOpen
519
+ expect(details[0].hasAttribute('open')).toBe(true)
520
+ // Item 2 should be closed
521
+ expect(details[1].hasAttribute('open')).toBe(false)
522
+ })
523
+ })
524
+
525
+ describe('Accessibility', () => {
526
+ test('has correct ARIA attributes', async () => {
527
+ const container = document.createElement('div')
528
+ container.innerHTML = `
529
+ <h2 id="accordion-heading">Main Accordion</h2>
530
+ <pkt-accordion aria-labelledby="accordion-heading">
531
+ <pkt-accordion-item id="test-item" title="Test Title">
532
+ Test content
533
+ </pkt-accordion-item>
534
+ </pkt-accordion>
535
+ `
536
+ document.body.appendChild(container)
537
+ await waitForCustomElements()
538
+
539
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
540
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
541
+ await accordion.updateComplete
542
+ await accordionItem.updateComplete
543
+
544
+ const accordionDiv = accordion.shadowRoot?.querySelector('.pkt-accordion')
545
+ const content = accordionItem.querySelector('.pkt-accordion-item__content')
546
+ const icon = accordionItem.querySelector('pkt-icon')
547
+
548
+ expect(accordionDiv?.getAttribute('aria-labelledby')).toBe('accordion-heading')
549
+ expect(content?.getAttribute('role')).toBe('region')
550
+ expect(icon?.getAttribute('aria-hidden')).toBe('true')
551
+ })
552
+
553
+ test('renders with no WCAG errors with axe - simple accordion', async () => {
554
+ const container = await createAccordion()
555
+
556
+ // Wait for all components to be fully rendered
557
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
558
+ const accordionItem = container.querySelector('pkt-accordion-item') as PktAccordionItem
559
+ await accordion.updateComplete
560
+ await accordionItem.updateComplete
561
+
562
+ const results = await axe(container)
563
+ expect(results).toHaveNoViolations()
564
+ })
565
+
566
+ test('renders with no WCAG errors with axe - complex accordion', async () => {
567
+ const container = document.createElement('div')
568
+ container.innerHTML = `
569
+ <h2 id="accordion-heading">Test Accordion Heading</h2>
570
+ <pkt-accordion skin="outlined" compact aria-labelledby="accordion-heading" name="test-group">
571
+ <pkt-accordion-item id="item1" title="First Item" default-open>
572
+ <p>This is the first accordion item content with <a href="#">a link</a>.</p>
573
+ </pkt-accordion-item>
574
+ <pkt-accordion-item id="item2" title="Second Item">
575
+ <div>
576
+ <h3>Nested content</h3>
577
+ <ul>
578
+ <li>List item 1</li>
579
+ <li>List item 2</li>
580
+ </ul>
581
+ </div>
582
+ </pkt-accordion-item>
583
+ <pkt-accordion-item id="item3" title="Third Item" skin="blue">
584
+ <form>
585
+ <label for="test-input">Test Input:</label>
586
+ <input type="text" id="test-input" name="test" />
587
+ <button type="submit">Submit</button>
588
+ </form>
589
+ </pkt-accordion-item>
590
+ </pkt-accordion>
591
+ `
592
+ document.body.appendChild(container)
593
+ await waitForCustomElements()
594
+
595
+ const accordion = container.querySelector('pkt-accordion') as PktAccordion
596
+ const accordionItems = container.querySelectorAll(
597
+ 'pkt-accordion-item',
598
+ ) as NodeListOf<PktAccordionItem>
599
+ await accordion.updateComplete
600
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
601
+
602
+ const results = await axe(container)
603
+ expect(results).toHaveNoViolations()
604
+ })
605
+
606
+ test('renders with no WCAG errors with axe - multiple accordions', async () => {
607
+ const container = document.createElement('div')
608
+ container.innerHTML = `
609
+ <div>
610
+ <h2 id="first-accordion-heading">First Accordion</h2>
611
+ <pkt-accordion aria-labelledby="first-accordion-heading" name="first-group">
612
+ <pkt-accordion-item id="first-item1" title="First Group Item 1">
613
+ Content for first group
614
+ </pkt-accordion-item>
615
+ <pkt-accordion-item id="first-item2" title="First Group Item 2">
616
+ More content for first group
617
+ </pkt-accordion-item>
618
+ </pkt-accordion>
619
+
620
+ <h2 id="second-accordion-heading">Second Accordion</h2>
621
+ <pkt-accordion aria-labelledby="second-accordion-heading" name="second-group" skin="beige">
622
+ <pkt-accordion-item id="second-item1" title="Second Group Item 1">
623
+ Content for second group
624
+ </pkt-accordion-item>
625
+ <pkt-accordion-item id="second-item2" title="Second Group Item 2">
626
+ More content for second group
627
+ </pkt-accordion-item>
628
+ </pkt-accordion>
629
+ </div>
630
+ `
631
+ document.body.appendChild(container)
632
+ await waitForCustomElements()
633
+
634
+ const accordions = container.querySelectorAll('pkt-accordion') as NodeListOf<PktAccordion>
635
+ const accordionItems = container.querySelectorAll(
636
+ 'pkt-accordion-item',
637
+ ) as NodeListOf<PktAccordionItem>
638
+ await Promise.all(Array.from(accordions).map((acc) => acc.updateComplete))
639
+ await Promise.all(Array.from(accordionItems).map((item) => item.updateComplete))
640
+
641
+ const results = await axe(container)
642
+ expect(results).toHaveNoViolations()
643
+ })
644
+ })