@oslokommune/punkt-elements 13.4.0 → 13.4.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 +17 -0
- package/dist/{alert-B0OUR9oD.js → alert-B07oUpkq.js} +5 -4
- package/dist/{alert-BH0lJ2ny.cjs → alert-DQNBDKjT.cjs} +1 -1
- package/dist/{backlink-B13JTp9D.js → backlink-C2jbzu0U.js} +16 -15
- package/dist/backlink-JbBNi3qg.cjs +13 -0
- package/dist/{button-CDocR7iN.cjs → button-B8rdtaHB.cjs} +1 -1
- package/dist/{button-DrrEvUy9.js → button-DhispFOY.js} +1 -0
- package/dist/{consent-CftYu8Di.js → consent-BpcQFvbi.js} +1 -1
- package/dist/{consent-DrS71kvz.cjs → consent-hYeFWNFr.cjs} +1 -1
- package/dist/pkt-alert.cjs +1 -1
- package/dist/pkt-alert.js +1 -1
- package/dist/pkt-backlink.cjs +1 -1
- package/dist/pkt-backlink.js +1 -1
- package/dist/pkt-button.cjs +1 -1
- package/dist/pkt-button.js +1 -1
- package/dist/pkt-consent.cjs +1 -1
- package/dist/pkt-consent.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +4 -4
- package/package.json +2 -2
- package/src/components/accordion/accordion.test.ts +0 -4
- package/src/components/alert/alert.test.ts +348 -0
- package/src/components/alert/alert.ts +1 -0
- package/src/components/backlink/backlink.test.ts +286 -0
- package/src/components/backlink/backlink.ts +5 -3
- package/src/components/button/button.test.ts +514 -0
- package/src/components/button/button.ts +2 -0
- package/dist/backlink-C5jQRMwJ.cjs +0 -13
|
@@ -0,0 +1,348 @@
|
|
|
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 './alert'
|
|
8
|
+
import { PktAlert } from './alert' // For type checking
|
|
9
|
+
|
|
10
|
+
const waitForCustomElements = async () => {
|
|
11
|
+
await customElements.whenDefined('pkt-alert')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Helper function to create alert markup
|
|
15
|
+
const createAlert = async (alertProps = '', content = 'Test alert content') => {
|
|
16
|
+
const container = document.createElement('div')
|
|
17
|
+
container.innerHTML = `
|
|
18
|
+
<pkt-alert ${alertProps}>
|
|
19
|
+
${content}
|
|
20
|
+
</pkt-alert>
|
|
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('PktAlert', () => {
|
|
33
|
+
describe('Rendering and basic functionality', () => {
|
|
34
|
+
test('renders without errors', async () => {
|
|
35
|
+
const container = await createAlert()
|
|
36
|
+
|
|
37
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
38
|
+
expect(alert).toBeInTheDocument()
|
|
39
|
+
|
|
40
|
+
await alert.updateComplete
|
|
41
|
+
expect(alert).toBeTruthy()
|
|
42
|
+
|
|
43
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
44
|
+
expect(alertDiv).toBeInTheDocument()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('renders with correct structure', async () => {
|
|
48
|
+
const container = await createAlert('title="Test Title"', 'Test alert message')
|
|
49
|
+
|
|
50
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
51
|
+
await alert.updateComplete
|
|
52
|
+
|
|
53
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
54
|
+
const grid = alertDiv?.querySelector('.pkt-alert__grid')
|
|
55
|
+
const icon = alert.querySelector('.pkt-alert__icon')
|
|
56
|
+
const title = alert.querySelector('.pkt-alert__title')
|
|
57
|
+
const text = alert.querySelector('.pkt-alert__text')
|
|
58
|
+
|
|
59
|
+
expect(alertDiv).toHaveClass('pkt-alert')
|
|
60
|
+
expect(grid).toHaveClass('pkt-alert__grid')
|
|
61
|
+
expect(icon).toBeInTheDocument()
|
|
62
|
+
expect(title).toBeInTheDocument()
|
|
63
|
+
expect(text).toBeInTheDocument()
|
|
64
|
+
expect(title?.textContent).toContain('Test Title')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('renders icon correctly based on skin', async () => {
|
|
68
|
+
const skins = ['error', 'success', 'warning', 'info'] as const
|
|
69
|
+
|
|
70
|
+
for (const skin of skins) {
|
|
71
|
+
const container = await createAlert(`skin="${skin}"`)
|
|
72
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
73
|
+
await alert.updateComplete
|
|
74
|
+
|
|
75
|
+
const icon = alert.querySelector('pkt-icon')
|
|
76
|
+
const expectedIconName = skin === 'info' ? 'alert-information' : `alert-${skin}`
|
|
77
|
+
|
|
78
|
+
expect(icon?.getAttribute('name')).toBe(expectedIconName)
|
|
79
|
+
expect(icon?.getAttribute('aria-hidden')).toBe('true')
|
|
80
|
+
|
|
81
|
+
// Cleanup for next iteration
|
|
82
|
+
container.remove()
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('Properties and attributes', () => {
|
|
88
|
+
test('applies default properties correctly', async () => {
|
|
89
|
+
const container = await createAlert()
|
|
90
|
+
|
|
91
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
92
|
+
await alert.updateComplete
|
|
93
|
+
|
|
94
|
+
expect(alert.skin).toBe('info')
|
|
95
|
+
expect(alert.compact).toBe(false)
|
|
96
|
+
expect(alert.closeAlert).toBe(false)
|
|
97
|
+
expect(alert.title).toBe('')
|
|
98
|
+
expect(alert.date).toBe(null)
|
|
99
|
+
expect(alert.role).toBe('status')
|
|
100
|
+
|
|
101
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
102
|
+
expect(alertDiv).toHaveClass('pkt-alert')
|
|
103
|
+
expect(alertDiv).toHaveClass('pkt-alert--info')
|
|
104
|
+
expect(alertDiv).not.toHaveClass('pkt-alert--compact')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('applies different skin properties correctly', async () => {
|
|
108
|
+
const skins = ['error', 'success', 'warning', 'info'] as const
|
|
109
|
+
|
|
110
|
+
for (const skin of skins) {
|
|
111
|
+
const container = await createAlert(`skin="${skin}"`)
|
|
112
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
113
|
+
await alert.updateComplete
|
|
114
|
+
|
|
115
|
+
expect(alert.skin).toBe(skin)
|
|
116
|
+
expect(alert.getAttribute('skin')).toBe(skin)
|
|
117
|
+
|
|
118
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
119
|
+
expect(alertDiv).toHaveClass(`pkt-alert--${skin}`)
|
|
120
|
+
|
|
121
|
+
// Cleanup for next iteration
|
|
122
|
+
container.remove()
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('applies compact property correctly', async () => {
|
|
127
|
+
const container = await createAlert('compact')
|
|
128
|
+
|
|
129
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
130
|
+
await alert.updateComplete
|
|
131
|
+
|
|
132
|
+
expect(alert.compact).toBe(true)
|
|
133
|
+
|
|
134
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
135
|
+
expect(alertDiv).toHaveClass('pkt-alert--compact')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('handles title property', async () => {
|
|
139
|
+
const container = await createAlert('title="Alert Title"')
|
|
140
|
+
|
|
141
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
142
|
+
await alert.updateComplete
|
|
143
|
+
|
|
144
|
+
expect(alert.title).toBe('Alert Title')
|
|
145
|
+
expect(alert.getAttribute('title')).toBe('Alert Title')
|
|
146
|
+
|
|
147
|
+
const title = alert.querySelector('.pkt-alert__title')
|
|
148
|
+
expect(title).toBeInTheDocument()
|
|
149
|
+
expect(title?.textContent).toBe('Alert Title')
|
|
150
|
+
|
|
151
|
+
// Test title updates
|
|
152
|
+
alert.title = 'Updated Title'
|
|
153
|
+
await alert.updateComplete
|
|
154
|
+
|
|
155
|
+
expect(title?.textContent).toBe('Updated Title')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('handles date property', async () => {
|
|
159
|
+
const container = await createAlert('date="2024-01-15"')
|
|
160
|
+
|
|
161
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
162
|
+
await alert.updateComplete
|
|
163
|
+
|
|
164
|
+
expect(alert.date).toBe('2024-01-15')
|
|
165
|
+
expect(alert.getAttribute('date')).toBe('2024-01-15')
|
|
166
|
+
|
|
167
|
+
const dateElement = alert.querySelector('.pkt-alert__date')
|
|
168
|
+
expect(dateElement).toBeInTheDocument()
|
|
169
|
+
expect(dateElement?.textContent).toContain('2024-01-15')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test('handles aria-live property', async () => {
|
|
173
|
+
const container = await createAlert('aria-live="assertive"')
|
|
174
|
+
|
|
175
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
176
|
+
await alert.updateComplete
|
|
177
|
+
|
|
178
|
+
// Note: The component sets aria-live to the ariaLive property value,
|
|
179
|
+
// which defaults to 'polite' from specs, not the attribute value
|
|
180
|
+
expect(alert['aria-live']).toBe('polite')
|
|
181
|
+
|
|
182
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
183
|
+
expect(alertDiv?.getAttribute('aria-live')).toBe('polite')
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
describe('Close functionality', () => {
|
|
188
|
+
test('renders close button when closeAlert is true', async () => {
|
|
189
|
+
const container = await createAlert('closealert')
|
|
190
|
+
|
|
191
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
192
|
+
await alert.updateComplete
|
|
193
|
+
|
|
194
|
+
expect(alert.closeAlert).toBe(true)
|
|
195
|
+
|
|
196
|
+
const closeDiv = alert.querySelector('.pkt-alert__close')
|
|
197
|
+
const closeButton = alert.querySelector('pkt-button')
|
|
198
|
+
|
|
199
|
+
expect(closeDiv).toBeInTheDocument()
|
|
200
|
+
expect(closeButton).toBeInTheDocument()
|
|
201
|
+
expect(closeButton?.getAttribute('aria-label')).toBe('close')
|
|
202
|
+
expect(closeButton?.getAttribute('skin')).toBe('tertiary')
|
|
203
|
+
expect(closeButton?.getAttribute('variant')).toBe('icon-only')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('does not render close button when closeAlert is false', async () => {
|
|
207
|
+
const container = await createAlert()
|
|
208
|
+
|
|
209
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
210
|
+
await alert.updateComplete
|
|
211
|
+
|
|
212
|
+
expect(alert.closeAlert).toBe(false)
|
|
213
|
+
|
|
214
|
+
const closeDiv = alert.querySelector('.pkt-alert__close')
|
|
215
|
+
const closeButton = alert.querySelector('pkt-button')
|
|
216
|
+
|
|
217
|
+
expect(closeDiv).not.toBeInTheDocument()
|
|
218
|
+
expect(closeButton).not.toBeInTheDocument()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('closes alert when close button is clicked', async () => {
|
|
222
|
+
const container = await createAlert('closealert')
|
|
223
|
+
|
|
224
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
225
|
+
await alert.updateComplete
|
|
226
|
+
|
|
227
|
+
// Listen for close events
|
|
228
|
+
const closeSpy = jest.fn()
|
|
229
|
+
const onCloseSpy = jest.fn()
|
|
230
|
+
alert.addEventListener('close', closeSpy)
|
|
231
|
+
alert.addEventListener('on-close', onCloseSpy)
|
|
232
|
+
|
|
233
|
+
const closeButton = alert.querySelector('pkt-button')
|
|
234
|
+
expect(closeButton).toBeInTheDocument()
|
|
235
|
+
|
|
236
|
+
// Click the close button
|
|
237
|
+
fireEvent.click(closeButton!)
|
|
238
|
+
await alert.updateComplete
|
|
239
|
+
|
|
240
|
+
// Alert should be hidden
|
|
241
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
242
|
+
expect(alertDiv).toHaveClass('pkt-hide')
|
|
243
|
+
expect(alert).toHaveClass('pkt-hide')
|
|
244
|
+
|
|
245
|
+
// Events should be dispatched
|
|
246
|
+
expect(closeSpy).toHaveBeenCalledWith(
|
|
247
|
+
expect.objectContaining({
|
|
248
|
+
type: 'close',
|
|
249
|
+
detail: expect.objectContaining({
|
|
250
|
+
origin: expect.any(Object),
|
|
251
|
+
}),
|
|
252
|
+
}),
|
|
253
|
+
)
|
|
254
|
+
expect(onCloseSpy).toHaveBeenCalledWith(
|
|
255
|
+
expect.objectContaining({
|
|
256
|
+
type: 'on-close',
|
|
257
|
+
detail: expect.objectContaining({
|
|
258
|
+
origin: expect.any(Object),
|
|
259
|
+
}),
|
|
260
|
+
}),
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('Grid layout classes', () => {
|
|
266
|
+
test('applies correct grid classes based on content', async () => {
|
|
267
|
+
// Test with no title and no date
|
|
268
|
+
const container1 = await createAlert()
|
|
269
|
+
const alert1 = container1.querySelector('pkt-alert') as PktAlert
|
|
270
|
+
await alert1.updateComplete
|
|
271
|
+
|
|
272
|
+
const grid1 = alert1.querySelector('.pkt-alert__grid')
|
|
273
|
+
expect(grid1).toHaveClass('pkt-alert__noTitle')
|
|
274
|
+
expect(grid1).toHaveClass('pkt-alert__noDate')
|
|
275
|
+
|
|
276
|
+
container1.remove()
|
|
277
|
+
|
|
278
|
+
// Test with title but no date
|
|
279
|
+
const container2 = await createAlert('title="Test Title"')
|
|
280
|
+
const alert2 = container2.querySelector('pkt-alert') as PktAlert
|
|
281
|
+
await alert2.updateComplete
|
|
282
|
+
|
|
283
|
+
const grid2 = alert2.querySelector('.pkt-alert__grid')
|
|
284
|
+
expect(grid2).not.toHaveClass('pkt-alert__noTitle')
|
|
285
|
+
expect(grid2).toHaveClass('pkt-alert__noDate')
|
|
286
|
+
|
|
287
|
+
container2.remove()
|
|
288
|
+
|
|
289
|
+
// Test with both title and date
|
|
290
|
+
const container3 = await createAlert('title="Test Title" date="2024-01-15"')
|
|
291
|
+
const alert3 = container3.querySelector('pkt-alert') as PktAlert
|
|
292
|
+
await alert3.updateComplete
|
|
293
|
+
|
|
294
|
+
const grid3 = alert3.querySelector('.pkt-alert__grid')
|
|
295
|
+
expect(grid3).not.toHaveClass('pkt-alert__noTitle')
|
|
296
|
+
expect(grid3).not.toHaveClass('pkt-alert__noDate')
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
describe('Accessibility', () => {
|
|
301
|
+
test('has correct ARIA attributes', async () => {
|
|
302
|
+
const container = await createAlert('aria-live="polite" role="alert"')
|
|
303
|
+
|
|
304
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
305
|
+
await alert.updateComplete
|
|
306
|
+
|
|
307
|
+
const alertDiv = alert.querySelector('.pkt-alert')
|
|
308
|
+
const icon = alert.querySelector('pkt-icon')
|
|
309
|
+
|
|
310
|
+
expect(alertDiv?.getAttribute('aria-live')).toBe('polite')
|
|
311
|
+
expect(alert.role).toBe('alert')
|
|
312
|
+
expect(icon?.getAttribute('aria-hidden')).toBe('true')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('renders with no WCAG errors with axe - simple alert', async () => {
|
|
316
|
+
const container = await createAlert('title="Test Alert"', 'This is a test alert message.')
|
|
317
|
+
|
|
318
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
319
|
+
await alert.updateComplete
|
|
320
|
+
|
|
321
|
+
const results = await axe(container)
|
|
322
|
+
expect(results).toHaveNoViolations()
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
test('renders with no WCAG errors with axe - complex alert', async () => {
|
|
326
|
+
const container = await createAlert(
|
|
327
|
+
'skin="error" title="Error Alert" date="2024-01-15" close-alert aria-live="assertive"',
|
|
328
|
+
'<p>This is an error message with <a href="#">a link</a>.</p>',
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
332
|
+
await alert.updateComplete
|
|
333
|
+
|
|
334
|
+
const results = await axe(container)
|
|
335
|
+
expect(results).toHaveNoViolations()
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('renders with no WCAG errors with axe - compact alert', async () => {
|
|
339
|
+
const container = await createAlert('skin="success" compact close-alert', 'Success message')
|
|
340
|
+
|
|
341
|
+
const alert = container.querySelector('pkt-alert') as PktAlert
|
|
342
|
+
await alert.updateComplete
|
|
343
|
+
|
|
344
|
+
const results = await axe(container)
|
|
345
|
+
expect(results).toHaveNoViolations()
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
})
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
|
|
4
|
+
expect.extend(toHaveNoViolations)
|
|
5
|
+
|
|
6
|
+
import './backlink'
|
|
7
|
+
import { PktBackLink } from './backlink' // For type checking
|
|
8
|
+
|
|
9
|
+
const waitForCustomElements = async () => {
|
|
10
|
+
await Promise.all([customElements.whenDefined('pkt-backlink')])
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Helper function to create backlink markup
|
|
14
|
+
const createBacklink = async (backlinkProps = '') => {
|
|
15
|
+
const container = document.createElement('div')
|
|
16
|
+
container.innerHTML = `
|
|
17
|
+
<pkt-backlink ${backlinkProps}></pkt-backlink>
|
|
18
|
+
`
|
|
19
|
+
document.body.appendChild(container)
|
|
20
|
+
await waitForCustomElements()
|
|
21
|
+
return container
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Cleanup after each test
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
document.body.innerHTML = ''
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('PktBackLink', () => {
|
|
30
|
+
describe('Rendering and basic functionality', () => {
|
|
31
|
+
test('renders without errors', async () => {
|
|
32
|
+
const container = await createBacklink()
|
|
33
|
+
|
|
34
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
35
|
+
expect(backlink).toBeInTheDocument()
|
|
36
|
+
|
|
37
|
+
await backlink.updateComplete
|
|
38
|
+
expect(backlink).toBeTruthy()
|
|
39
|
+
|
|
40
|
+
const nav = backlink.querySelector('nav')
|
|
41
|
+
expect(nav).toBeInTheDocument()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('renders with correct structure', async () => {
|
|
45
|
+
const container = await createBacklink()
|
|
46
|
+
|
|
47
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
48
|
+
await backlink.updateComplete
|
|
49
|
+
|
|
50
|
+
const nav = backlink.querySelector('nav')
|
|
51
|
+
const link = nav?.querySelector('a')
|
|
52
|
+
const icon = link?.querySelector('pkt-icon')
|
|
53
|
+
const textSpan = link?.querySelector('.pkt-back-link__text')
|
|
54
|
+
|
|
55
|
+
expect(nav).toHaveClass('pkt-back-link')
|
|
56
|
+
expect(link).toHaveClass('pkt-link')
|
|
57
|
+
expect(link).toHaveClass('pkt-link--icon-left')
|
|
58
|
+
expect(icon).toHaveClass('pkt-back-link__icon')
|
|
59
|
+
expect(icon).toHaveClass('pkt-icon')
|
|
60
|
+
expect(icon).toHaveClass('pkt-link__icon')
|
|
61
|
+
expect(textSpan).toHaveClass('pkt-back-link__text')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('renders icon correctly', async () => {
|
|
65
|
+
const container = await createBacklink()
|
|
66
|
+
|
|
67
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
68
|
+
await backlink.updateComplete
|
|
69
|
+
|
|
70
|
+
const icon = backlink.querySelector('pkt-icon')
|
|
71
|
+
expect(icon).toBeInTheDocument()
|
|
72
|
+
expect(icon?.getAttribute('name')).toBe('chevron-thin-left')
|
|
73
|
+
expect(icon?.getAttribute('aria-hidden')).toBe('true')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('Properties and attributes', () => {
|
|
78
|
+
test('applies default properties correctly', async () => {
|
|
79
|
+
const container = await createBacklink()
|
|
80
|
+
|
|
81
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
82
|
+
await backlink.updateComplete
|
|
83
|
+
|
|
84
|
+
expect(backlink.href).toBe('')
|
|
85
|
+
expect(backlink.text).toBe('Forsiden')
|
|
86
|
+
expect(backlink.ariaLabel).toBe('')
|
|
87
|
+
|
|
88
|
+
const nav = backlink.querySelector('nav')
|
|
89
|
+
const link = nav?.querySelector('a')
|
|
90
|
+
const textSpan = link?.querySelector('.pkt-back-link__text')
|
|
91
|
+
|
|
92
|
+
expect(link?.getAttribute('href')).toBe('/')
|
|
93
|
+
expect(textSpan?.textContent).toBe('Forsiden')
|
|
94
|
+
expect(nav?.getAttribute('aria-label')).toBe('Gå tilbake til forrige side')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('handles href property correctly', async () => {
|
|
98
|
+
const container = await createBacklink('href="/previous-page"')
|
|
99
|
+
|
|
100
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
101
|
+
await backlink.updateComplete
|
|
102
|
+
|
|
103
|
+
expect(backlink.href).toBe('/previous-page')
|
|
104
|
+
|
|
105
|
+
const link = backlink.querySelector('a')
|
|
106
|
+
expect(link?.getAttribute('href')).toBe('/previous-page')
|
|
107
|
+
|
|
108
|
+
// Test href updates
|
|
109
|
+
backlink.href = '/another-page'
|
|
110
|
+
await backlink.updateComplete
|
|
111
|
+
|
|
112
|
+
expect(link?.getAttribute('href')).toBe('/another-page')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('handles text property correctly', async () => {
|
|
116
|
+
const container = await createBacklink('text="Back to Home"')
|
|
117
|
+
|
|
118
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
119
|
+
await backlink.updateComplete
|
|
120
|
+
|
|
121
|
+
expect(backlink.text).toBe('Back to Home')
|
|
122
|
+
// The text attribute should be removed from the custom element
|
|
123
|
+
expect(backlink.getAttribute('text')).toBe(null)
|
|
124
|
+
|
|
125
|
+
const textSpan = backlink.querySelector('.pkt-back-link__text')
|
|
126
|
+
expect(textSpan?.textContent).toBe('Back to Home')
|
|
127
|
+
|
|
128
|
+
// Test text updates
|
|
129
|
+
backlink.text = 'Return to Start'
|
|
130
|
+
await backlink.updateComplete
|
|
131
|
+
|
|
132
|
+
expect(textSpan?.textContent).toBe('Return to Start')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('handles ariaLabel property correctly', async () => {
|
|
136
|
+
// Set the property directly instead of using attribute
|
|
137
|
+
const container = await createBacklink()
|
|
138
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
139
|
+
|
|
140
|
+
// Set ariaLabel property directly
|
|
141
|
+
backlink.ariaLabel = 'Navigate back to previous section'
|
|
142
|
+
await backlink.updateComplete
|
|
143
|
+
|
|
144
|
+
expect(backlink.ariaLabel).toBe('Navigate back to previous section')
|
|
145
|
+
|
|
146
|
+
const nav = backlink.querySelector('nav')
|
|
147
|
+
expect(nav?.getAttribute('aria-label')).toBe('Navigate back to previous section')
|
|
148
|
+
|
|
149
|
+
// Test ariaLabel updates
|
|
150
|
+
backlink.ariaLabel = 'Go back to main menu'
|
|
151
|
+
await backlink.updateComplete
|
|
152
|
+
|
|
153
|
+
expect(nav?.getAttribute('aria-label')).toBe('Go back to main menu')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('uses default aria-label when none provided', async () => {
|
|
157
|
+
const container = await createBacklink()
|
|
158
|
+
|
|
159
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
160
|
+
await backlink.updateComplete
|
|
161
|
+
|
|
162
|
+
expect(backlink.ariaLabel).toBe('')
|
|
163
|
+
|
|
164
|
+
const nav = backlink.querySelector('nav')
|
|
165
|
+
expect(nav?.getAttribute('aria-label')).toBe('Gå tilbake til forrige side')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('handles empty href by defaulting to "/"', async () => {
|
|
169
|
+
const container = await createBacklink('href=""')
|
|
170
|
+
|
|
171
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
172
|
+
await backlink.updateComplete
|
|
173
|
+
|
|
174
|
+
expect(backlink.href).toBe('')
|
|
175
|
+
|
|
176
|
+
const link = backlink.querySelector('a')
|
|
177
|
+
expect(link?.getAttribute('href')).toBe('/')
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe('Attribute cleanup', () => {
|
|
182
|
+
test('removes reflected attributes from the element', async () => {
|
|
183
|
+
const container = await createBacklink('href="/test" text="Test Text"')
|
|
184
|
+
|
|
185
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
186
|
+
await backlink.updateComplete
|
|
187
|
+
|
|
188
|
+
// The attributeChangedCallback should remove these attributes from the element
|
|
189
|
+
// (this is implementation-specific behavior in the component)
|
|
190
|
+
expect(backlink.hasAttribute('href')).toBe(false)
|
|
191
|
+
expect(backlink.hasAttribute('text')).toBe(false)
|
|
192
|
+
expect(backlink.hasAttribute('arialabel')).toBe(false)
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
describe('Link functionality', () => {
|
|
197
|
+
test('creates clickable link with correct href', async () => {
|
|
198
|
+
const container = await createBacklink('href="/dashboard" text="Back to Dashboard"')
|
|
199
|
+
|
|
200
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
201
|
+
await backlink.updateComplete
|
|
202
|
+
|
|
203
|
+
const link = backlink.querySelector('a')
|
|
204
|
+
expect(link).toBeInTheDocument()
|
|
205
|
+
expect(link?.getAttribute('href')).toBe('/dashboard')
|
|
206
|
+
expect(link?.textContent?.trim()).toContain('Back to Dashboard')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('handles complex URLs correctly', async () => {
|
|
210
|
+
const complexUrl = 'https://example.com/path?query=value#section'
|
|
211
|
+
const container = await createBacklink(`href="${complexUrl}"`)
|
|
212
|
+
|
|
213
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
214
|
+
await backlink.updateComplete
|
|
215
|
+
|
|
216
|
+
const link = backlink.querySelector('a')
|
|
217
|
+
expect(link?.getAttribute('href')).toBe(complexUrl)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('Accessibility', () => {
|
|
222
|
+
test('has correct ARIA attributes', async () => {
|
|
223
|
+
const container = await createBacklink('text="Home"')
|
|
224
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
225
|
+
|
|
226
|
+
// Set ariaLabel property to test custom aria-label
|
|
227
|
+
backlink.ariaLabel = 'Return to main page'
|
|
228
|
+
await backlink.updateComplete
|
|
229
|
+
|
|
230
|
+
const nav = backlink.querySelector('nav')
|
|
231
|
+
const icon = backlink.querySelector('pkt-icon')
|
|
232
|
+
|
|
233
|
+
expect(nav?.getAttribute('aria-label')).toBe('Return to main page')
|
|
234
|
+
expect(icon?.getAttribute('aria-hidden')).toBe('true')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('provides semantic navigation structure', async () => {
|
|
238
|
+
const container = await createBacklink()
|
|
239
|
+
|
|
240
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
241
|
+
await backlink.updateComplete
|
|
242
|
+
|
|
243
|
+
const nav = backlink.querySelector('nav')
|
|
244
|
+
const link = nav?.querySelector('a')
|
|
245
|
+
|
|
246
|
+
expect(nav).toBeInTheDocument()
|
|
247
|
+
expect(link).toBeInTheDocument()
|
|
248
|
+
expect(nav?.tagName.toLowerCase()).toBe('nav')
|
|
249
|
+
expect(link?.tagName.toLowerCase()).toBe('a')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('renders with no WCAG errors with axe - default backlink', async () => {
|
|
253
|
+
const container = await createBacklink()
|
|
254
|
+
|
|
255
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
256
|
+
await backlink.updateComplete
|
|
257
|
+
|
|
258
|
+
const results = await axe(container)
|
|
259
|
+
expect(results).toHaveNoViolations()
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test('renders with no WCAG errors with axe - custom backlink', async () => {
|
|
263
|
+
const container = await createBacklink(
|
|
264
|
+
'href="/categories" text="Back to Categories" aria-label="Navigate back to category listing"',
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
268
|
+
await backlink.updateComplete
|
|
269
|
+
|
|
270
|
+
const results = await axe(container)
|
|
271
|
+
expect(results).toHaveNoViolations()
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test('renders with no WCAG errors with axe - external link', async () => {
|
|
275
|
+
const container = await createBacklink(
|
|
276
|
+
'href="https://example.com" text="Back to External Site"',
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
const backlink = container.querySelector('pkt-backlink') as PktBackLink
|
|
280
|
+
await backlink.updateComplete
|
|
281
|
+
|
|
282
|
+
const results = await axe(container)
|
|
283
|
+
expect(results).toHaveNoViolations()
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
})
|
|
@@ -3,6 +3,8 @@ import { html } from 'lit'
|
|
|
3
3
|
import { property, customElement } from 'lit/decorators.js'
|
|
4
4
|
import { ifDefined } from 'lit/directives/if-defined.js'
|
|
5
5
|
|
|
6
|
+
import '@/components/icon'
|
|
7
|
+
|
|
6
8
|
export interface IPktBackLink {
|
|
7
9
|
href?: string
|
|
8
10
|
text?: string
|
|
@@ -13,9 +15,9 @@ export interface IPktBackLink {
|
|
|
13
15
|
export class PktBackLink extends PktElement<IPktBackLink> implements IPktBackLink {
|
|
14
16
|
// Properties
|
|
15
17
|
|
|
16
|
-
@property({ type: String
|
|
17
|
-
@property({ type: String
|
|
18
|
-
@property({ type: String
|
|
18
|
+
@property({ type: String }) href: string = ''
|
|
19
|
+
@property({ type: String }) text: string = 'Forsiden'
|
|
20
|
+
@property({ type: String }) ariaLabel: string = ''
|
|
19
21
|
|
|
20
22
|
// Lifecycle
|
|
21
23
|
|