@oslokommune/punkt-elements 13.3.1 → 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 +17 -0
- package/package.json +15 -4
- package/src/components/accordion/accordion.test.ts +644 -0
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
|
+
"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": {
|
|
38
|
+
"@babel/preset-env": "^7.28.3",
|
|
33
39
|
"@oslokommune/punkt-assets": "^13.3.1",
|
|
34
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": "
|
|
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
|
+
})
|