@oslokommune/punkt-elements 13.4.0 → 13.4.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{alert-B0OUR9oD.js → alert-B07oUpkq.js} +5 -4
  3. package/dist/{alert-BH0lJ2ny.cjs → alert-DQNBDKjT.cjs} +1 -1
  4. package/dist/{backlink-B13JTp9D.js → backlink-C2jbzu0U.js} +16 -15
  5. package/dist/backlink-JbBNi3qg.cjs +13 -0
  6. package/dist/{button-CDocR7iN.cjs → button-B8rdtaHB.cjs} +1 -1
  7. package/dist/{button-DrrEvUy9.js → button-DhispFOY.js} +1 -0
  8. package/dist/{card-uccD6Pnv.cjs → card-BUITGoqX.cjs} +10 -10
  9. package/dist/{card-BI1NZONj.js → card-Dtw26f7i.js} +96 -76
  10. package/dist/checkbox-Gn7Wtk9h.cjs +31 -0
  11. package/dist/checkbox-ym7z6cpt.js +142 -0
  12. package/dist/{combobox-BhcqC30d.cjs → combobox-DjO0RMUB.cjs} +1 -1
  13. package/dist/{combobox-D9dGKWuZ.js → combobox-yE4aYhTi.js} +1 -1
  14. package/dist/{consent-CftYu8Di.js → consent-BpcQFvbi.js} +1 -1
  15. package/dist/{consent-DrS71kvz.cjs → consent-hYeFWNFr.cjs} +1 -1
  16. package/dist/index.d.ts +3 -0
  17. package/dist/pkt-alert.cjs +1 -1
  18. package/dist/pkt-alert.js +1 -1
  19. package/dist/pkt-backlink.cjs +1 -1
  20. package/dist/pkt-backlink.js +1 -1
  21. package/dist/pkt-button.cjs +1 -1
  22. package/dist/pkt-button.js +1 -1
  23. package/dist/pkt-card.cjs +1 -1
  24. package/dist/pkt-card.js +1 -1
  25. package/dist/pkt-checkbox.cjs +1 -1
  26. package/dist/pkt-checkbox.js +1 -1
  27. package/dist/pkt-combobox.cjs +1 -1
  28. package/dist/pkt-combobox.js +1 -1
  29. package/dist/pkt-consent.cjs +1 -1
  30. package/dist/pkt-consent.js +1 -1
  31. package/dist/pkt-index.cjs +1 -1
  32. package/dist/pkt-index.js +7 -7
  33. package/package.json +2 -2
  34. package/src/components/accordion/accordion.test.ts +0 -4
  35. package/src/components/alert/alert.test.ts +348 -0
  36. package/src/components/alert/alert.ts +1 -0
  37. package/src/components/backlink/backlink.test.ts +286 -0
  38. package/src/components/backlink/backlink.ts +5 -3
  39. package/src/components/button/button.test.ts +514 -0
  40. package/src/components/button/button.ts +2 -0
  41. package/src/components/card/card.test.ts +592 -0
  42. package/src/components/card/card.ts +24 -1
  43. package/src/components/checkbox/checkbox.test.ts +535 -0
  44. package/src/components/checkbox/checkbox.ts +44 -1
  45. package/src/components/combobox/combobox.test.ts +737 -0
  46. package/src/components/combobox/combobox.ts +1 -1
  47. package/dist/backlink-C5jQRMwJ.cjs +0 -13
  48. package/dist/checkbox-CTRbpbye.js +0 -120
  49. package/dist/checkbox-wJ26voZd.cjs +0 -30
@@ -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
+ })
@@ -9,6 +9,7 @@ import { updateClassAttribute } from '@/utils/classutils'
9
9
  import specs from 'componentSpecs/alert.json'
10
10
 
11
11
  import '@/components/icon'
12
+ import '@/components/button'
12
13
 
13
14
  export type TAlertSkin = 'error' | 'success' | 'warning' | 'info'
14
15
 
@@ -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, reflect: true }) href: string = ''
17
- @property({ type: String, reflect: true }) text: string = 'Forsiden'
18
- @property({ type: String, reflect: true }) ariaLabel: 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