@oslokommune/punkt-elements 13.5.2 → 13.5.4

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 (33) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{alert-DQNBDKjT.cjs → alert-7rUOhlNi.cjs} +2 -1
  3. package/dist/{alert-B07oUpkq.js → alert-cUBtwi2k.js} +12 -11
  4. package/dist/{loader-CHPxY9c6.cjs → loader-DNidjwH-.cjs} +6 -1
  5. package/dist/{loader-Da4IOk_T.js → loader-h3d-3D7s.js} +6 -1
  6. package/dist/{messagebox-DwGdcdm7.js → messagebox-C8KQgCl_.js} +14 -13
  7. package/dist/{messagebox-CqUBJs_D.cjs → messagebox-CjPtPPrW.cjs} +1 -0
  8. package/dist/pkt-alert.cjs +1 -1
  9. package/dist/pkt-alert.js +1 -1
  10. package/dist/pkt-index.cjs +1 -1
  11. package/dist/pkt-index.js +3 -3
  12. package/dist/pkt-loader.cjs +1 -1
  13. package/dist/pkt-loader.js +1 -1
  14. package/dist/pkt-messagebox.cjs +1 -1
  15. package/dist/pkt-messagebox.js +1 -1
  16. package/package.json +6 -2
  17. package/src/components/alert/alert.test.ts +64 -79
  18. package/src/components/alert/alert.ts +1 -0
  19. package/src/components/backlink/backlink.test.ts +50 -96
  20. package/src/components/button/button.test.ts +211 -249
  21. package/src/components/calendar/calendar.accessibility.test.ts +30 -43
  22. package/src/components/card/card.test.ts +71 -121
  23. package/src/components/checkbox/checkbox.test.ts +231 -156
  24. package/src/components/consent/consent.test.ts +87 -91
  25. package/src/components/icon/icon.test.ts +368 -0
  26. package/src/components/input-wrapper/input-wrapper.test.ts +505 -0
  27. package/src/components/link/link.test.ts +224 -0
  28. package/src/components/linkcard/linkcard.test.ts +14 -12
  29. package/src/components/listbox/listbox.test.ts +225 -0
  30. package/src/components/loader/loader.test.ts +257 -0
  31. package/src/components/loader/loader.ts +6 -1
  32. package/src/components/messagebox/messagebox.test.ts +241 -0
  33. package/src/components/messagebox/messagebox.ts +1 -0
@@ -0,0 +1,224 @@
1
+ import '@testing-library/jest-dom'
2
+ import { axe, toHaveNoViolations } from 'jest-axe'
3
+ import { fireEvent } from '@testing-library/dom'
4
+ import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
5
+ import { CustomElementFor } from '../../tests/component-registry'
6
+ import './link'
7
+
8
+ expect.extend(toHaveNoViolations)
9
+
10
+ export interface LinkTestConfig extends BaseTestConfig {
11
+ href?: string
12
+ target?: string
13
+ skin?: string
14
+ variant?: string
15
+ iconName?: string
16
+ iconPosition?: string
17
+ openInNewTab?: boolean
18
+ external?: boolean
19
+ }
20
+
21
+ // Use shared framework
22
+ export const createLinkTest = async (config: LinkTestConfig = {}) => {
23
+ const { container, element } = await createElementTest<
24
+ CustomElementFor<'pkt-link'>,
25
+ LinkTestConfig
26
+ >('pkt-link', config)
27
+
28
+ return {
29
+ container,
30
+ link: element,
31
+ }
32
+ }
33
+
34
+ describe('PktLink', () => {
35
+ describe('Rendering and basic functionality', () => {
36
+ test('renders without errors', async () => {
37
+ const { link } = await createLinkTest()
38
+
39
+ expect(link).toBeInTheDocument()
40
+ await link.updateComplete
41
+ expect(link).toBeTruthy()
42
+ })
43
+
44
+ test('renders with correct structure', async () => {
45
+ const { link } = await createLinkTest({
46
+ href: 'https://example.com',
47
+ content: 'Click me',
48
+ })
49
+ await link.updateComplete
50
+
51
+ expect(link).toBeInTheDocument()
52
+ const anchor = link.querySelector('a')
53
+ expect(anchor).toBeInTheDocument()
54
+ expect(anchor?.href).toBe('https://example.com/')
55
+ expect(anchor?.textContent).toContain('Click me')
56
+ })
57
+ })
58
+
59
+ describe('Properties and attributes', () => {
60
+ test('applies default properties correctly', async () => {
61
+ const { link } = await createLinkTest()
62
+ await link.updateComplete
63
+
64
+ expect(link.href).toBe('#')
65
+ expect(link.external).toBe(false)
66
+ expect(link.iconName).toBeUndefined()
67
+ expect(link.target).toBe('_self')
68
+ })
69
+
70
+ test('sets href property correctly', async () => {
71
+ const { link } = await createLinkTest({
72
+ href: 'https://example.com',
73
+ })
74
+ await link.updateComplete
75
+
76
+ expect(link.href).toBe('https://example.com')
77
+ const anchor = link.querySelector('a')
78
+ expect(anchor?.href).toBe('https://example.com/')
79
+ })
80
+
81
+ test('sets external property correctly', async () => {
82
+ const { link } = await createLinkTest({
83
+ href: 'https://example.com',
84
+ external: true,
85
+ })
86
+ await link.updateComplete
87
+
88
+ expect(link.external).toBe(true)
89
+ })
90
+ })
91
+
92
+ describe('Icon functionality', () => {
93
+ test('renders icon when iconName is provided', async () => {
94
+ const { link } = await createLinkTest({
95
+ href: '#',
96
+ iconName: 'arrow-right',
97
+ })
98
+ await link.updateComplete
99
+
100
+ const icon = link.querySelector('pkt-icon')
101
+ expect(icon).toBeInTheDocument()
102
+ expect(icon?.getAttribute('name')).toBe('arrow-right')
103
+ })
104
+
105
+ test('positions icon correctly', async () => {
106
+ const { link } = await createLinkTest({
107
+ href: '#',
108
+ iconName: 'arrow-right',
109
+ iconPosition: 'right',
110
+ })
111
+ await link.updateComplete
112
+
113
+ const anchor = link.querySelector('a')
114
+ expect(anchor?.classList.contains('pkt-link--icon-right')).toBe(true)
115
+ })
116
+ })
117
+
118
+ describe('External link functionality', () => {
119
+ test('applies external class and rel attribute', async () => {
120
+ const { link } = await createLinkTest({
121
+ href: 'https://example.com',
122
+ external: true,
123
+ })
124
+ await link.updateComplete
125
+
126
+ const anchor = link.querySelector('a')
127
+ expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
128
+ expect(anchor?.rel).toBe('noopener noreferrer')
129
+ })
130
+
131
+ test('does not set rel attribute for internal links', async () => {
132
+ const { link } = await createLinkTest({
133
+ href: '/internal-page',
134
+ })
135
+ await link.updateComplete
136
+
137
+ const anchor = link.querySelector('a')
138
+ expect(anchor?.rel).toBe('')
139
+ })
140
+ })
141
+
142
+ describe('Event handling', () => {
143
+ test('handles click events', async () => {
144
+ const { link } = await createLinkTest({
145
+ href: '#test',
146
+ })
147
+ await link.updateComplete
148
+
149
+ const anchor = link.querySelector('a')
150
+ const clickHandler = jest.fn()
151
+ anchor?.addEventListener('click', clickHandler)
152
+
153
+ fireEvent.click(anchor!)
154
+ expect(clickHandler).toHaveBeenCalled()
155
+ })
156
+ })
157
+
158
+ describe('Dynamic updates', () => {
159
+ test('updates href dynamically', async () => {
160
+ const { link } = await createLinkTest({
161
+ href: 'https://example.com',
162
+ })
163
+ await link.updateComplete
164
+
165
+ link.href = 'https://updated.com'
166
+ await link.updateComplete
167
+
168
+ expect(link.href).toBe('https://updated.com')
169
+ const anchor = link.querySelector('a')
170
+ expect(anchor?.href).toBe('https://updated.com/')
171
+ })
172
+
173
+ test('updates external property dynamically', async () => {
174
+ const { link } = await createLinkTest({
175
+ href: 'https://example.com',
176
+ })
177
+ await link.updateComplete
178
+
179
+ link.external = true
180
+ await link.updateComplete
181
+
182
+ expect(link.external).toBe(true)
183
+ const anchor = link.querySelector('a')
184
+ expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
185
+ expect(anchor?.rel).toBe('noopener noreferrer')
186
+ })
187
+ })
188
+
189
+ describe('Accessibility', () => {
190
+ test('basic link is accessible', async () => {
191
+ const { container } = await createLinkTest({
192
+ href: 'https://example.com',
193
+ content: 'Accessible Link',
194
+ })
195
+ await new Promise((resolve) => setTimeout(resolve, 100))
196
+
197
+ const results = await axe(container)
198
+ expect(results).toHaveNoViolations()
199
+ })
200
+ })
201
+
202
+ describe('Integration scenarios', () => {
203
+ test('works with complex configurations', async () => {
204
+ const { link } = await createLinkTest({
205
+ href: 'https://external-site.com',
206
+ iconName: 'external',
207
+ iconPosition: 'right',
208
+ external: true,
209
+ target: '_blank',
210
+ content: 'Complex External Link',
211
+ })
212
+ await link.updateComplete
213
+
214
+ expect(link.href).toBe('https://external-site.com')
215
+ expect(link.iconName).toBe('external')
216
+ expect(link.external).toBe(true)
217
+ expect(link.target).toBe('_blank')
218
+
219
+ const anchor = link.querySelector('a')
220
+ expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
221
+ expect(anchor?.classList.contains('pkt-link--icon-right')).toBe(true)
222
+ })
223
+ })
224
+ })
@@ -6,14 +6,11 @@ import { PktLinkCard, type TLinkCardSkin } from './linkcard'
6
6
  import type { IPktLinkCard } from './linkcard'
7
7
  import './linkcard'
8
8
 
9
-
10
- // Enhanced test configuration extends the component interface with additional test-specific properties
11
9
  export interface LinkCardTestConfig extends IPktLinkCard, BaseTestConfig {}
12
10
 
13
-
14
11
  // Use shared framework
15
12
  export const createLinkCardTest = async (config: LinkCardTestConfig = {}) => {
16
- const { container, element} = await createElementTest<
13
+ const { container, element } = await createElementTest<
17
14
  CustomElementFor<'pkt-linkcard'>,
18
15
  LinkCardTestConfig
19
16
  >('pkt-linkcard', config)
@@ -30,7 +27,14 @@ expect.extend(toHaveNoViolations)
30
27
 
31
28
  // Test data constants
32
29
  const VALID_SKINS: TLinkCardSkin[] = [
33
- 'normal', 'no-padding', 'blue', 'beige', 'green', 'gray', 'beige-outline', 'gray-outline'
30
+ 'normal',
31
+ 'no-padding',
32
+ 'blue',
33
+ 'beige',
34
+ 'green',
35
+ 'gray',
36
+ 'beige-outline',
37
+ 'gray-outline',
34
38
  ]
35
39
 
36
40
  const SAMPLE_ICONS = ['arrow-right', 'external-link', 'download', 'info']
@@ -238,7 +242,7 @@ describe('PktLinkCard', () => {
238
242
  iconName: 'arrow-right',
239
243
  openInNewTab: true,
240
244
  skin: 'blue',
241
- content: '<p>Complete content</p>'
245
+ content: '<p>Complete content</p>',
242
246
  }
243
247
 
244
248
  const { linkCard, link } = await createLinkCardTest(config)
@@ -283,7 +287,7 @@ describe('PktLinkCard', () => {
283
287
  const { linkCard } = await createLinkCardTest({
284
288
  title: '',
285
289
  href: '',
286
- iconName: ''
290
+ iconName: '',
287
291
  })
288
292
 
289
293
  expect(linkCard.title).toBe('')
@@ -315,7 +319,7 @@ describe('PktLinkCard', () => {
315
319
  test('has no accessibility violations', async () => {
316
320
  const { linkCard } = await createLinkCardTest({
317
321
  title: 'Accessible Link Card',
318
- href: '/accessible'
322
+ href: '/accessible',
319
323
  })
320
324
 
321
325
  const results = await axe(linkCard)
@@ -325,7 +329,7 @@ describe('PktLinkCard', () => {
325
329
  test('maintains proper link semantics', async () => {
326
330
  const { link } = await createLinkCardTest({
327
331
  title: 'Semantic Link',
328
- href: '/semantic'
332
+ href: '/semantic',
329
333
  })
330
334
 
331
335
  expect(link.tagName).toBe('A')
@@ -337,7 +341,7 @@ describe('PktLinkCard', () => {
337
341
  const { link } = await createLinkCardTest({
338
342
  title: 'External Link',
339
343
  href: 'https://example.com',
340
- openInNewTab: true
344
+ openInNewTab: true,
341
345
  })
342
346
 
343
347
  expect(link.getAttribute('target')).toBe('_blank')
@@ -345,7 +349,6 @@ describe('PktLinkCard', () => {
345
349
  })
346
350
  })
347
351
 
348
-
349
352
  describe('Type Safety', () => {
350
353
  test('validates interface implementation', () => {
351
354
  const linkCard = new PktLinkCard()
@@ -359,4 +362,3 @@ describe('PktLinkCard', () => {
359
362
  })
360
363
  })
361
364
  })
362
-
@@ -0,0 +1,225 @@
1
+ import '@testing-library/jest-dom'
2
+ import { axe, toHaveNoViolations } from 'jest-axe'
3
+ import { fireEvent } from '@testing-library/dom'
4
+ import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
5
+ import { CustomElementFor } from '../../tests/component-registry'
6
+ import { type IPktListbox } from './listbox'
7
+ import './listbox'
8
+
9
+ export interface ListboxTestConfig extends Partial<IPktListbox>, BaseTestConfig {
10
+ label?: string
11
+ id?: string
12
+ }
13
+
14
+ // Use shared framework
15
+ export const createListboxTest = async (config: ListboxTestConfig = {}) => {
16
+ const { container, element } = await createElementTest<
17
+ CustomElementFor<'pkt-listbox'>,
18
+ ListboxTestConfig
19
+ >('pkt-listbox', { ...config, options: undefined })
20
+
21
+ // Set complex properties directly on the element after creation
22
+ if (config.options) {
23
+ element.options = config.options
24
+ await element.updateComplete
25
+ }
26
+
27
+ return {
28
+ container,
29
+ listbox: element,
30
+ }
31
+ }
32
+
33
+ expect.extend(toHaveNoViolations)
34
+
35
+ // Cleanup after each test
36
+ afterEach(() => {
37
+ document.body.innerHTML = ''
38
+ })
39
+
40
+ describe('PktListbox', () => {
41
+ describe('Rendering and basic functionality', () => {
42
+ test('renders without errors', async () => {
43
+ const { listbox } = await createListboxTest()
44
+
45
+ expect(listbox).toBeInTheDocument()
46
+ expect(listbox).toBeTruthy()
47
+ })
48
+
49
+ test('renders with options', async () => {
50
+ const options = [
51
+ { value: 'option1', label: 'Option 1' },
52
+ { value: 'option2', label: 'Option 2' },
53
+ ]
54
+ const { listbox } = await createListboxTest({
55
+ label: 'Test Listbox',
56
+ options,
57
+ })
58
+
59
+ const listboxElement = listbox.querySelector('.pkt-listbox')
60
+ expect(listboxElement).toBeInTheDocument()
61
+
62
+ const optionElements = listbox.querySelectorAll('.pkt-listbox__option')
63
+ expect(optionElements).toHaveLength(2)
64
+ })
65
+ })
66
+
67
+ describe('Properties and attributes', () => {
68
+ test('applies default properties correctly', async () => {
69
+ const { listbox } = await createListboxTest()
70
+
71
+ expect(listbox.isOpen).toBe(false)
72
+ expect(listbox.disabled).toBe(false)
73
+ expect(listbox.includeSearch).toBe(false)
74
+ expect(listbox.isMultiSelect).toBe(false)
75
+ expect(listbox.allowUserInput).toBe(false)
76
+ expect(listbox.maxLength).toBe(0)
77
+ })
78
+
79
+ test('sets properties correctly', async () => {
80
+ const { listbox } = await createListboxTest({
81
+ isOpen: true,
82
+ disabled: true,
83
+ includeSearch: true,
84
+ isMultiSelect: true,
85
+ })
86
+
87
+ expect(listbox.isOpen).toBe(true)
88
+ expect(listbox.disabled).toBe(true)
89
+ expect(listbox.includeSearch).toBe(true)
90
+ expect(listbox.isMultiSelect).toBe(true)
91
+ })
92
+ })
93
+
94
+ describe('Option handling', () => {
95
+ test('renders single select options correctly', async () => {
96
+ const options = [
97
+ { value: 'option1', label: 'Option 1', selected: true },
98
+ { value: 'option2', label: 'Option 2' },
99
+ ]
100
+ const { listbox } = await createListboxTest({ options })
101
+
102
+ const selectedOption = listbox.querySelector('.pkt-listbox__option--selected')
103
+ expect(selectedOption).toBeInTheDocument()
104
+
105
+ const checkIcon = listbox.querySelector('pkt-icon[name="check-big"]')
106
+ expect(checkIcon).toBeInTheDocument()
107
+ })
108
+
109
+ test('renders multi-select options with checkboxes', async () => {
110
+ const options = [
111
+ { value: 'option1', label: 'Option 1', selected: true },
112
+ { value: 'option2', label: 'Option 2' },
113
+ ]
114
+ const { listbox } = await createListboxTest({
115
+ isMultiSelect: true,
116
+ options,
117
+ })
118
+
119
+ const checkboxes = listbox.querySelectorAll('input[type="checkbox"]')
120
+ expect(checkboxes).toHaveLength(2)
121
+ expect(checkboxes[0]).toBeChecked()
122
+ expect(checkboxes[1]).not.toBeChecked()
123
+ })
124
+
125
+ test('handles option click', async () => {
126
+ const options = [
127
+ { value: 'option1', label: 'Option 1' },
128
+ { value: 'option2', label: 'Option 2' },
129
+ ]
130
+ const { listbox } = await createListboxTest({ options })
131
+
132
+ // Listen for the option-toggle event
133
+ let toggledValue: string | null = null
134
+ listbox.addEventListener('option-toggle', (e: any) => {
135
+ toggledValue = e.detail
136
+ })
137
+
138
+ const optionElement = listbox.querySelector('.pkt-listbox__option')
139
+ fireEvent.click(optionElement!)
140
+
141
+ await listbox.updateComplete
142
+ expect(toggledValue).toBe('option1')
143
+ })
144
+ })
145
+
146
+ describe('Search functionality', () => {
147
+ test('renders search when includeSearch is true', async () => {
148
+ const { listbox } = await createListboxTest({ includeSearch: true })
149
+
150
+ const searchInput = listbox.querySelector('[role="searchbox"]')
151
+ expect(searchInput).toBeInTheDocument()
152
+ })
153
+
154
+ test('filters options based on search', async () => {
155
+ const options = [
156
+ { value: 'apple', label: 'Apple' },
157
+ { value: 'banana', label: 'Banana' },
158
+ ]
159
+ const { listbox } = await createListboxTest({
160
+ includeSearch: true,
161
+ options,
162
+ })
163
+
164
+ // Set search value and trigger filtering
165
+ listbox.searchValue = 'app'
166
+ listbox.filterOptions()
167
+ await listbox.updateComplete
168
+
169
+ // Should filter to only show Apple - check filtered options
170
+ const visibleOptions = listbox.querySelectorAll('.pkt-listbox__option')
171
+ expect(visibleOptions).toHaveLength(1)
172
+ expect(visibleOptions[0].textContent?.trim()).toContain('Apple')
173
+ })
174
+ })
175
+
176
+ describe('User input functionality', () => {
177
+ test('renders new option banner when allowUserInput is true', async () => {
178
+ const { listbox } = await createListboxTest({
179
+ allowUserInput: true,
180
+ customUserInput: 'New',
181
+ })
182
+
183
+ const newOptionBanner = listbox.querySelector('.pkt-listbox__banner--new-option')
184
+ expect(newOptionBanner).toBeInTheDocument()
185
+ expect(newOptionBanner?.getAttribute('data-value')).toBe('New')
186
+ // Check that the text contains the basic structure
187
+ expect(newOptionBanner?.textContent).toMatch(/Legg til.*New/)
188
+ })
189
+ })
190
+
191
+ describe('Maximum selection', () => {
192
+ test('shows maximum reached banner', async () => {
193
+ const options = [
194
+ { value: 'option1', label: 'Option 1', selected: true },
195
+ { value: 'option2', label: 'Option 2', selected: true },
196
+ ]
197
+ const { listbox } = await createListboxTest({
198
+ isMultiSelect: true,
199
+ maxLength: 3,
200
+ options,
201
+ })
202
+
203
+ const banner = listbox.querySelector('.pkt-listbox__banner--maximum-reached')
204
+ expect(banner).toBeInTheDocument()
205
+ expect(banner?.textContent).toContain('2 av maks 3')
206
+ })
207
+ })
208
+
209
+ describe('Accessibility', () => {
210
+ test('basic listbox is accessible', async () => {
211
+ const options = [
212
+ { value: 'option1', label: 'Option 1' },
213
+ { value: 'option2', label: 'Option 2' },
214
+ ]
215
+ const { container } = await createListboxTest({
216
+ label: 'Accessible Listbox',
217
+ options,
218
+ })
219
+ await new Promise((resolve) => setTimeout(resolve, 100))
220
+
221
+ const results = await axe(container)
222
+ expect(results).toHaveNoViolations()
223
+ })
224
+ })
225
+ })