@oslokommune/punkt-elements 13.5.1 → 13.5.3
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 +34 -0
- package/dist/{alert-DQNBDKjT.cjs → alert-7rUOhlNi.cjs} +2 -1
- package/dist/{alert-B07oUpkq.js → alert-cUBtwi2k.js} +12 -11
- package/dist/{linkcard-BVEsUPwG.js → linkcard-9CNlyT0S.js} +17 -16
- package/dist/{linkcard-BlMhPNry.cjs → linkcard-DqIvb54H.cjs} +2 -2
- package/dist/pkt-alert.cjs +1 -1
- package/dist/pkt-alert.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +2 -2
- package/dist/pkt-linkcard.cjs +1 -1
- package/dist/pkt-linkcard.js +1 -1
- package/package.json +6 -2
- package/src/components/alert/alert.test.ts +64 -79
- package/src/components/alert/alert.ts +1 -0
- package/src/components/backlink/backlink.test.ts +50 -96
- package/src/components/button/button.test.ts +211 -249
- package/src/components/calendar/calendar.accessibility.test.ts +30 -43
- package/src/components/card/card.test.ts +71 -121
- package/src/components/checkbox/checkbox.test.ts +231 -156
- package/src/components/consent/consent.test.ts +87 -91
- package/src/components/icon/icon.test.ts +368 -0
- package/src/components/input-wrapper/input-wrapper.test.ts +505 -0
- package/src/components/link/link.test.ts +224 -0
- package/src/components/linkcard/linkcard.test.ts +364 -0
- package/src/components/linkcard/linkcard.ts +3 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import {
|
|
4
|
+
createElementTest,
|
|
5
|
+
BaseTestConfig,
|
|
6
|
+
setupConsoleMocking,
|
|
7
|
+
restoreConsoleMocking,
|
|
8
|
+
} from '../../tests/test-framework'
|
|
9
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
10
|
+
import './icon'
|
|
11
|
+
import { PktIcon } from './icon'
|
|
12
|
+
|
|
13
|
+
expect.extend(toHaveNoViolations)
|
|
14
|
+
|
|
15
|
+
export interface IconTestConfig extends BaseTestConfig {
|
|
16
|
+
name?: string
|
|
17
|
+
path?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Use shared framework
|
|
21
|
+
export const createIconTest = async (config: IconTestConfig = {}) => {
|
|
22
|
+
const { container, element } = await createElementTest<
|
|
23
|
+
CustomElementFor<'pkt-icon'>,
|
|
24
|
+
IconTestConfig
|
|
25
|
+
>('pkt-icon', config)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
container,
|
|
29
|
+
icon: element,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cleanup after each test
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
document.body.innerHTML = ''
|
|
36
|
+
// Clean up localStorage after tests
|
|
37
|
+
localStorage.clear()
|
|
38
|
+
// Reset global variables
|
|
39
|
+
delete (window as any).pktFetch
|
|
40
|
+
delete (window as any).pktIconPath
|
|
41
|
+
// Restore console mocking
|
|
42
|
+
restoreConsoleMocking()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Mock fetch for icon loading
|
|
46
|
+
const mockFetch = jest.fn()
|
|
47
|
+
const mockSvgContent =
|
|
48
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="test-path"></path></svg>'
|
|
49
|
+
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
// Setup console mocking to suppress error logs during tests
|
|
52
|
+
setupConsoleMocking()
|
|
53
|
+
|
|
54
|
+
// Setup default mocks
|
|
55
|
+
mockFetch.mockResolvedValue({
|
|
56
|
+
ok: true,
|
|
57
|
+
text: () => Promise.resolve(mockSvgContent),
|
|
58
|
+
})
|
|
59
|
+
window.pktFetch = mockFetch
|
|
60
|
+
window.pktIconPath = 'https://test-cdn.example.com/icons/'
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('PktIcon', () => {
|
|
64
|
+
describe('Rendering and basic functionality', () => {
|
|
65
|
+
test('renders without errors', async () => {
|
|
66
|
+
const { icon } = await createIconTest()
|
|
67
|
+
|
|
68
|
+
expect(icon).toBeInTheDocument()
|
|
69
|
+
await icon.updateComplete
|
|
70
|
+
expect(icon.classList.contains('pkt-icon')).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('renders with default structure', async () => {
|
|
74
|
+
const { icon } = await createIconTest({
|
|
75
|
+
name: 'arrow-right',
|
|
76
|
+
})
|
|
77
|
+
await icon.updateComplete
|
|
78
|
+
|
|
79
|
+
expect(icon).toBeInTheDocument()
|
|
80
|
+
expect(icon.classList.contains('pkt-icon')).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('renders nothing when no name is provided', async () => {
|
|
84
|
+
const { icon } = await createIconTest()
|
|
85
|
+
await icon.updateComplete
|
|
86
|
+
|
|
87
|
+
// Should render nothing meaningful when no name is provided (only Lit template comments)
|
|
88
|
+
expect(icon.innerHTML).not.toContain('<svg')
|
|
89
|
+
expect(icon.name).toBe('')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('Properties and attributes', () => {
|
|
94
|
+
test('applies default properties correctly', async () => {
|
|
95
|
+
const { icon } = await createIconTest()
|
|
96
|
+
await icon.updateComplete
|
|
97
|
+
|
|
98
|
+
expect(icon.name).toBe('')
|
|
99
|
+
expect(icon.path).toBe('https://test-cdn.example.com/icons/')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('sets name property correctly', async () => {
|
|
103
|
+
const { icon } = await createIconTest({
|
|
104
|
+
name: 'arrow-right',
|
|
105
|
+
})
|
|
106
|
+
await icon.updateComplete
|
|
107
|
+
|
|
108
|
+
expect(icon.name).toBe('arrow-right')
|
|
109
|
+
expect(icon.getAttribute('name')).toBe('arrow-right')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('sets path property correctly', async () => {
|
|
113
|
+
const customPath = 'https://custom-cdn.example.com/icons/'
|
|
114
|
+
const { icon } = await createIconTest({
|
|
115
|
+
path: customPath,
|
|
116
|
+
name: 'arrow-right',
|
|
117
|
+
})
|
|
118
|
+
await icon.updateComplete
|
|
119
|
+
|
|
120
|
+
expect(icon.path).toBe(customPath)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('uses global pktIconPath when path not specified', async () => {
|
|
124
|
+
window.pktIconPath = 'https://global-cdn.example.com/icons/'
|
|
125
|
+
const { icon } = await createIconTest({
|
|
126
|
+
name: 'arrow-right',
|
|
127
|
+
})
|
|
128
|
+
await icon.updateComplete
|
|
129
|
+
|
|
130
|
+
expect(icon.path).toBe('https://global-cdn.example.com/icons/')
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('Icon loading functionality', () => {
|
|
135
|
+
test('fetches icon from CDN when name is provided', async () => {
|
|
136
|
+
const { icon } = await createIconTest({
|
|
137
|
+
name: 'arrow-right',
|
|
138
|
+
})
|
|
139
|
+
await icon.updateComplete
|
|
140
|
+
|
|
141
|
+
// Allow some time for async icon loading
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
143
|
+
|
|
144
|
+
expect(mockFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/arrow-right.svg')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('caches loaded icons in localStorage', async () => {
|
|
148
|
+
const { icon } = await createIconTest({
|
|
149
|
+
name: 'arrow-right',
|
|
150
|
+
})
|
|
151
|
+
await icon.updateComplete
|
|
152
|
+
|
|
153
|
+
// Wait for icon to load
|
|
154
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
155
|
+
|
|
156
|
+
const cachedIcon = localStorage.getItem('https://test-cdn.example.com/icons/arrow-right.svg')
|
|
157
|
+
expect(cachedIcon).toBe(mockSvgContent)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('uses cached icon when available', async () => {
|
|
161
|
+
// Pre-populate cache
|
|
162
|
+
localStorage.setItem('https://test-cdn.example.com/icons/cached-icon.svg', mockSvgContent)
|
|
163
|
+
|
|
164
|
+
const { icon } = await createIconTest({
|
|
165
|
+
name: 'cached-icon',
|
|
166
|
+
})
|
|
167
|
+
await icon.updateComplete
|
|
168
|
+
|
|
169
|
+
// Should not fetch since it's cached
|
|
170
|
+
expect(mockFetch).not.toHaveBeenCalledWith(
|
|
171
|
+
'https://test-cdn.example.com/icons/cached-icon.svg',
|
|
172
|
+
)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('handles fetch errors gracefully', async () => {
|
|
176
|
+
mockFetch.mockResolvedValueOnce({
|
|
177
|
+
ok: false,
|
|
178
|
+
text: () => Promise.resolve(''),
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const { icon } = await createIconTest({
|
|
182
|
+
name: 'missing-icon',
|
|
183
|
+
})
|
|
184
|
+
await icon.updateComplete
|
|
185
|
+
|
|
186
|
+
// Should log error and use error SVG
|
|
187
|
+
expect(mockFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/missing-icon.svg')
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('Dynamic updates', () => {
|
|
192
|
+
test('updates icon when name changes', async () => {
|
|
193
|
+
const { icon } = await createIconTest({
|
|
194
|
+
name: 'arrow-right',
|
|
195
|
+
})
|
|
196
|
+
await icon.updateComplete
|
|
197
|
+
|
|
198
|
+
// Change the name
|
|
199
|
+
icon.name = 'arrow-left'
|
|
200
|
+
await icon.updateComplete
|
|
201
|
+
|
|
202
|
+
expect(icon.name).toBe('arrow-left')
|
|
203
|
+
expect(icon.getAttribute('name')).toBe('arrow-left')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('updates icon when path changes', async () => {
|
|
207
|
+
const { icon } = await createIconTest({
|
|
208
|
+
name: 'arrow-right',
|
|
209
|
+
})
|
|
210
|
+
await icon.updateComplete
|
|
211
|
+
|
|
212
|
+
const newPath = 'https://new-cdn.example.com/icons/'
|
|
213
|
+
icon.path = newPath
|
|
214
|
+
await icon.updateComplete
|
|
215
|
+
|
|
216
|
+
expect(icon.path).toBe(newPath)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('re-fetches icon when path changes', async () => {
|
|
220
|
+
const { icon } = await createIconTest({
|
|
221
|
+
name: 'arrow-right',
|
|
222
|
+
})
|
|
223
|
+
await icon.updateComplete
|
|
224
|
+
|
|
225
|
+
// Allow initial load to complete
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
227
|
+
|
|
228
|
+
mockFetch.mockClear()
|
|
229
|
+
// Setup mock again for the new path
|
|
230
|
+
mockFetch.mockResolvedValue({
|
|
231
|
+
ok: true,
|
|
232
|
+
text: () => Promise.resolve(mockSvgContent),
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const newPath = 'https://new-cdn.example.com/icons/'
|
|
236
|
+
|
|
237
|
+
// Try setting attribute to trigger attributeChangedCallback
|
|
238
|
+
icon.setAttribute('path', newPath)
|
|
239
|
+
await icon.updateComplete
|
|
240
|
+
|
|
241
|
+
// Allow some time for async icon loading
|
|
242
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
243
|
+
|
|
244
|
+
expect(mockFetch).toHaveBeenCalledWith('https://new-cdn.example.com/icons/arrow-right.svg')
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe('CSS classes and styling', () => {
|
|
249
|
+
test('applies pkt-icon class', async () => {
|
|
250
|
+
const { icon } = await createIconTest({
|
|
251
|
+
name: 'arrow-right',
|
|
252
|
+
})
|
|
253
|
+
await icon.updateComplete
|
|
254
|
+
|
|
255
|
+
expect(icon.classList.contains('pkt-icon')).toBe(true)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('maintains pkt-icon class after updates', async () => {
|
|
259
|
+
const { icon } = await createIconTest({
|
|
260
|
+
name: 'arrow-right',
|
|
261
|
+
})
|
|
262
|
+
await icon.updateComplete
|
|
263
|
+
|
|
264
|
+
icon.name = 'arrow-left'
|
|
265
|
+
await icon.updateComplete
|
|
266
|
+
|
|
267
|
+
expect(icon.classList.contains('pkt-icon')).toBe(true)
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Global configuration', () => {
|
|
272
|
+
test('uses custom pktFetch function when provided', async () => {
|
|
273
|
+
const customFetch = jest.fn().mockResolvedValue({
|
|
274
|
+
ok: true,
|
|
275
|
+
text: () => Promise.resolve('<svg>custom</svg>'),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
window.pktFetch = customFetch
|
|
279
|
+
|
|
280
|
+
const { icon } = await createIconTest({
|
|
281
|
+
name: 'custom-icon',
|
|
282
|
+
})
|
|
283
|
+
await icon.updateComplete
|
|
284
|
+
|
|
285
|
+
// Allow some time for async icon loading
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
287
|
+
|
|
288
|
+
expect(customFetch).toHaveBeenCalledWith('https://test-cdn.example.com/icons/custom-icon.svg')
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('falls back to error SVG when pktFetch is not available', async () => {
|
|
292
|
+
delete (window as any).pktFetch
|
|
293
|
+
|
|
294
|
+
const { icon } = await createIconTest({
|
|
295
|
+
name: 'fallback-icon',
|
|
296
|
+
})
|
|
297
|
+
await icon.updateComplete
|
|
298
|
+
|
|
299
|
+
// Allow some time for async icon loading
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
301
|
+
|
|
302
|
+
// Should render error SVG in light DOM when fetch is not available
|
|
303
|
+
expect(icon.innerHTML).toContain('viewBox="0 0 32 32"')
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
describe('Accessibility', () => {
|
|
308
|
+
test('basic icon is accessible', async () => {
|
|
309
|
+
const { container } = await createIconTest({
|
|
310
|
+
name: 'arrow-right',
|
|
311
|
+
})
|
|
312
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
313
|
+
|
|
314
|
+
const results = await axe(container)
|
|
315
|
+
expect(results).toHaveNoViolations()
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
test('icon with custom path is accessible', async () => {
|
|
319
|
+
const { container } = await createIconTest({
|
|
320
|
+
name: 'arrow-right',
|
|
321
|
+
path: 'https://custom-cdn.example.com/icons/',
|
|
322
|
+
})
|
|
323
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
324
|
+
|
|
325
|
+
const results = await axe(container)
|
|
326
|
+
expect(results).toHaveNoViolations()
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('Integration scenarios', () => {
|
|
331
|
+
test('works with multiple icons simultaneously', async () => {
|
|
332
|
+
const container = document.createElement('div')
|
|
333
|
+
container.innerHTML = `
|
|
334
|
+
<pkt-icon name="arrow-right"></pkt-icon>
|
|
335
|
+
<pkt-icon name="arrow-left"></pkt-icon>
|
|
336
|
+
<pkt-icon name="close"></pkt-icon>
|
|
337
|
+
`
|
|
338
|
+
document.body.appendChild(container)
|
|
339
|
+
|
|
340
|
+
// Wait for elements to be defined
|
|
341
|
+
await customElements.whenDefined('pkt-icon')
|
|
342
|
+
|
|
343
|
+
const icons = container.querySelectorAll('pkt-icon')
|
|
344
|
+
expect(icons).toHaveLength(3)
|
|
345
|
+
|
|
346
|
+
for (const icon of icons) {
|
|
347
|
+
await (icon as PktIcon).updateComplete
|
|
348
|
+
expect(icon.classList.contains('pkt-icon')).toBe(true)
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test('handles rapid name changes correctly', async () => {
|
|
353
|
+
const { icon } = await createIconTest({
|
|
354
|
+
name: 'arrow-right',
|
|
355
|
+
})
|
|
356
|
+
await icon.updateComplete
|
|
357
|
+
|
|
358
|
+
// Rapidly change names
|
|
359
|
+
icon.name = 'arrow-left'
|
|
360
|
+
icon.name = 'close'
|
|
361
|
+
icon.name = 'menu'
|
|
362
|
+
await icon.updateComplete
|
|
363
|
+
|
|
364
|
+
expect(icon.name).toBe('menu')
|
|
365
|
+
expect(icon.getAttribute('name')).toBe('menu')
|
|
366
|
+
})
|
|
367
|
+
})
|
|
368
|
+
})
|