@oslokommune/punkt-react 16.26.3 → 17.0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/components/accordion/Accordion.d.ts +1 -0
  3. package/dist/components/alert/Alert.d.ts +9 -0
  4. package/dist/components/button/Button.d.ts +1 -1
  5. package/dist/components/combobox/types.d.ts +2 -0
  6. package/dist/components/datepicker/types.d.ts +3 -0
  7. package/dist/components/fileupload/FileUpload.d.ts +12 -2
  8. package/dist/components/footer/Footer.accessibility.test.d.ts +0 -0
  9. package/dist/components/footer/Footer.d.ts +12 -34
  10. package/dist/components/footer/Footer.test.d.ts +1 -0
  11. package/dist/components/footer/FooterGlobal.d.ts +23 -0
  12. package/dist/components/footerSimple/FooterSimple.d.ts +19 -16
  13. package/dist/components/footerSimple/FooterSimple.test.d.ts +1 -0
  14. package/dist/components/header/Header.d.ts +15 -3
  15. package/dist/components/header/HeaderGlobal.accessibility.test.d.ts +0 -0
  16. package/dist/components/header/HeaderGlobal.d.ts +2 -0
  17. package/dist/components/header/HeaderGlobal.test.d.ts +0 -0
  18. package/dist/components/header/HeaderService.accessibility.test.d.ts +0 -0
  19. package/dist/components/header/types.d.ts +30 -2
  20. package/dist/components/header-menu/HeaderMenu.d.ts +2 -4
  21. package/dist/components/index.d.ts +4 -0
  22. package/dist/components/inputwrapper/InputWrapper.d.ts +1 -0
  23. package/dist/components/searchinput/SearchInput.d.ts +1 -0
  24. package/dist/components/select/Select.d.ts +4 -7
  25. package/dist/components/textarea/Textarea.d.ts +2 -0
  26. package/dist/components/textinput/Textinput.d.ts +1 -0
  27. package/dist/components/timepicker/types.d.ts +3 -0
  28. package/dist/components/timepicker/useTimepickerState.d.ts +2 -0
  29. package/dist/punkt-react.cjs +1 -1
  30. package/dist/punkt-react.js +3191 -2727
  31. package/dist/shared-types/footer.d.ts +95 -0
  32. package/dist/shared-types/{header-menu.d.ts → header-footer.d.ts} +51 -4
  33. package/dist/shared-types/header.d.ts +1 -1
  34. package/dist/shared-types/index.d.ts +6 -4
  35. package/dist/shared-types/searchinput.d.ts +2 -0
  36. package/dist/shared-types/select.d.ts +3 -2
  37. package/dist/shared-types/size.d.ts +4 -0
  38. package/dist/shared-types/timepicker.d.ts +0 -6
  39. package/dist/shared-utils/datepicker-utils.d.ts +5 -4
  40. package/dist/shared-utils/fileupload/focus-trap.d.ts +4 -10
  41. package/dist/shared-utils/focus-trap/index.d.ts +22 -0
  42. package/dist/shared-utils/{header-menu → header-footer}/example-data.d.ts +5 -4
  43. package/dist/shared-utils/header-footer/external-domain.d.ts +26 -0
  44. package/dist/shared-utils/{header-menu → header-footer}/fetch-header-footer-data.d.ts +6 -2
  45. package/dist/shared-utils/{header-menu → header-footer}/index.d.ts +2 -1
  46. package/dist/shared-utils/{header-menu → header-footer}/select-megamenu.d.ts +1 -1
  47. package/dist/shared-utils/scroll-lock/index.d.ts +24 -0
  48. package/package.json +4 -4
  49. package/src/components/accordion/Accordion.test.tsx +51 -0
  50. package/src/components/accordion/Accordion.tsx +3 -3
  51. package/src/components/accordion/AccordionItem.tsx +7 -14
  52. package/src/components/alert/Alert.test.tsx +45 -9
  53. package/src/components/alert/Alert.tsx +13 -3
  54. package/src/components/button/Button.tsx +2 -2
  55. package/src/components/combobox/Combobox.core.test.tsx +14 -0
  56. package/src/components/combobox/Combobox.tsx +1 -0
  57. package/src/components/combobox/ComboboxInput.tsx +2 -0
  58. package/src/components/combobox/types.ts +2 -0
  59. package/src/components/combobox/useComboboxState.ts +2 -0
  60. package/src/components/consent/Consent.test.tsx +8 -14
  61. package/src/components/consent/Consent.tsx +9 -19
  62. package/src/components/datepicker/Datepicker.core.test.tsx +11 -0
  63. package/src/components/datepicker/Datepicker.tsx +4 -1
  64. package/src/components/datepicker/DatepickerInputs.tsx +16 -1
  65. package/src/components/datepicker/types.ts +3 -0
  66. package/src/components/datepicker/useDatepickerState.ts +17 -9
  67. package/src/components/fileupload/FileUpload.tsx +27 -9
  68. package/src/components/footer/Footer.accessibility.test.tsx +46 -0
  69. package/src/components/footer/Footer.test.tsx +192 -86
  70. package/src/components/footer/Footer.tsx +169 -159
  71. package/src/components/footer/FooterGlobal.tsx +181 -0
  72. package/src/components/footerSimple/FooterSimple.test.tsx +49 -46
  73. package/src/components/footerSimple/FooterSimple.tsx +117 -78
  74. package/src/components/header/Header.tsx +17 -4
  75. package/src/components/header/HeaderGlobal.accessibility.test.tsx +86 -0
  76. package/src/components/header/HeaderGlobal.test.tsx +254 -0
  77. package/src/components/header/HeaderGlobal.tsx +467 -0
  78. package/src/components/header/HeaderService.accessibility.test.tsx +70 -0
  79. package/src/components/header/HeaderService.test.tsx +29 -16
  80. package/src/components/header/HeaderService.tsx +24 -84
  81. package/src/components/header/types.ts +35 -0
  82. package/src/components/header-menu/HeaderMenu.test.tsx +16 -39
  83. package/src/components/header-menu/HeaderMenu.tsx +62 -70
  84. package/src/components/index.ts +4 -0
  85. package/src/components/inputwrapper/InputWrapper.tsx +6 -1
  86. package/src/components/modal/Modal.tsx +7 -11
  87. package/src/components/searchinput/SearchInput.test.tsx +10 -0
  88. package/src/components/searchinput/SearchInput.tsx +19 -7
  89. package/src/components/select/Select.test.tsx +18 -0
  90. package/src/components/select/Select.tsx +8 -4
  91. package/src/components/textarea/Textarea.test.tsx +10 -0
  92. package/src/components/textarea/Textarea.tsx +5 -1
  93. package/src/components/textinput/Textinput.test.tsx +28 -0
  94. package/src/components/textinput/Textinput.tsx +5 -2
  95. package/src/components/timepicker/Timepicker.test.tsx +10 -0
  96. package/src/components/timepicker/Timepicker.tsx +8 -7
  97. package/src/components/timepicker/types.ts +8 -5
  98. package/src/components/timepicker/useTimepickerState.ts +4 -0
  99. package/dist/shared-utils/modal-scroll-lock.d.ts +0 -10
  100. /package/dist/shared-utils/{header-menu → header-footer}/icon-map.d.ts +0 -0
@@ -1,111 +1,217 @@
1
- // Test med axe https://www.npmjs.com/package/jest-axe
2
- import '@testing-library/jest-dom'
1
+ import { render, screen, waitFor } from '@testing-library/react'
2
+ import { afterEach, describe, expect, test, vi } from 'vitest'
3
3
 
4
- import { axe, toHaveNoViolations } from 'jest-axe'
5
-
6
- import { render, screen } from '../../utils/test-utils'
4
+ import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer'
5
+ import type { IPktFooterColumn } from 'shared-types'
7
6
  import { PktFooter } from './Footer'
8
7
 
9
- expect.extend(toHaveNoViolations)
10
-
11
- describe('PktFooter', () => {
12
- const columnOne = {
13
- title: 'Column One',
8
+ const COLUMNS: IPktFooterColumn[] = [
9
+ {
10
+ title: 'Overskrift 1',
11
+ text: 'Her kan du skrive en lengre tekst som beskriver løsningen.',
12
+ links: [{ text: 'Lenke 1', href: 'https://example.test/lenke-1' }],
13
+ },
14
+ {
15
+ title: 'Overskrift 2',
14
16
  links: [
15
- {
16
- href: 'https://example.com',
17
- text: 'Link 1',
18
- external: true,
19
- },
20
- {
21
- href: 'https://example.com',
22
- text: 'Link 2',
23
- },
17
+ { text: 'Lenke 2', href: 'https://example.test/lenke-2', external: true },
18
+ { text: 'Lenke 3', href: 'https://example.test/lenke-3', openInNewTab: true },
24
19
  ],
25
- text: 'Column One Text',
26
- }
20
+ },
21
+ ]
27
22
 
28
- const columnTwo = {
29
- title: 'Column Two',
30
- links: [
31
- {
32
- href: 'https://example.com',
33
- text: 'Link 3',
34
- external: true,
35
- },
36
- {
37
- href: 'https://example.com',
38
- text: 'Link 4',
39
- },
40
- ],
41
- text: 'Column Two Text',
42
- }
23
+ afterEach(() => {
24
+ vi.restoreAllMocks()
25
+ })
43
26
 
44
- const socialLinks = [
45
- {
46
- href: 'https://example.com',
47
- iconName: 'facebook',
48
- },
49
- {
50
- href: 'https://example.com',
51
- iconName: 'twitter',
52
- },
53
- ]
27
+ describe('PktFooter global footer', () => {
28
+ test('rendrer kun global footer uten custom innhold', () => {
29
+ const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} />)
30
+ expect(container.querySelector('.pkt-footer__band--secondary')).toBeNull()
31
+ expect(container.querySelector('.pkt-footer__band--global')).not.toBeNull()
32
+ const titles = [...container.querySelectorAll('.pkt-footer__band--global h2')].map(
33
+ (t) => t.textContent,
34
+ )
35
+ expect(titles).toEqual(['Kontakt', 'Jobb i Oslo kommune', 'Om nettstedet'])
36
+ })
54
37
 
55
- describe('basic rendering', () => {
56
- it('should render columns and social links', () => {
57
- render(<PktFooter columnOne={columnOne} columnTwo={columnTwo} socialLinks={socialLinks} />)
38
+ test('rendrer bunnrad med språklenke og some-ikoner', () => {
39
+ const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} />)
40
+ const bottom = container.querySelector('.pkt-footer__band--global .pkt-footer__bottom')
41
+ expect(bottom?.textContent).toContain('English')
42
+ const someLinks = [...container.querySelectorAll('.pkt-footer__band--global .pkt-footer__some-link')]
43
+ expect(someLinks).toHaveLength(3)
44
+ expect(someLinks[0].getAttribute('aria-label')).toBe('Facebook')
45
+ })
58
46
 
59
- const columnOneElement = screen.getByText('Column One')
60
- expect(columnOneElement).toBeInTheDocument()
47
+ test('merker eksterne lenker med toppdomene i parentes', () => {
48
+ render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} />)
49
+ const jobLink = screen.getByRole('link', { name: /Ledige stillinger/ })
50
+ expect(jobLink.querySelector('.pkt-footer__link-domain')?.textContent?.trim()).toBe('(webcruiter.com)')
51
+ const internalLink = screen.getByRole('link', { name: 'Tilgjengelighet' })
52
+ expect(internalLink.querySelector('.pkt-footer__link-domain')).toBeNull()
53
+ })
54
+
55
+ test('skjuler #cb-trigger-lenken uten includeConsent', () => {
56
+ const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} />)
57
+ expect(container.textContent).not.toContain('Innstillinger for informasjonskapsler')
58
+ })
61
59
 
62
- const columnOneLink1Element = screen.getByText('Link 1')
63
- expect(columnOneLink1Element).toHaveAttribute('href', 'https://example.com')
64
- expect(columnOneLink1Element).toHaveClass('pkt-link--external')
60
+ test('erstatter #cb-trigger med consent-lenke når includeConsent er satt', () => {
61
+ const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} includeConsent />)
62
+ const consentLink = [...container.querySelectorAll('a.pkt-footer__link')].find((link) =>
63
+ link.textContent?.includes('Innstillinger for informasjonskapsler'),
64
+ )
65
+ expect(consentLink).toBeDefined()
66
+ expect(consentLink?.closest('.pkt-footer__band--global')).not.toBeNull()
67
+ })
68
+
69
+ test('bruker valgt locale', () => {
70
+ const { container } = render(<PktFooter data={EXAMPLE_HEADER_FOOTER_DATA} locale="en-GB" />)
71
+ const titles = [...container.querySelectorAll('.pkt-footer__band--global h2')].map(
72
+ (t) => t.textContent,
73
+ )
74
+ expect(titles).toContain('Contact us')
75
+ })
65
76
 
66
- const columnOneLink2Element = screen.getByText('Link 2')
67
- expect(columnOneLink2Element).toHaveAttribute('href', 'https://example.com')
68
- expect(columnOneLink2Element).not.toHaveClass('pkt-link--external')
77
+ test('skipGlobal dropper global footer og henter ikke data', () => {
78
+ const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('should not run'))
79
+ const { container } = render(<PktFooter skipGlobal columns={COLUMNS} />)
80
+ expect(container.querySelector('.pkt-footer__band--global')).toBeNull()
81
+ expect(container.querySelector('.pkt-footer__band--secondary')).not.toBeNull()
82
+ expect(fetchSpy).not.toHaveBeenCalled()
83
+ })
84
+ })
69
85
 
70
- const columnOneTextElement = screen.getByText('Column One Text')
71
- expect(columnOneTextElement).toBeInTheDocument()
86
+ describe('PktFooter henting av data', () => {
87
+ test('henter data fra endepunkt når data ikke er satt', async () => {
88
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(
89
+ new Response(JSON.stringify(EXAMPLE_HEADER_FOOTER_DATA), {
90
+ status: 200,
91
+ headers: { 'Content-Type': 'application/json' },
92
+ }),
93
+ )
94
+ const { container } = render(<PktFooter />)
95
+ await waitFor(() => expect(container.querySelector('.pkt-footer__band--global')).not.toBeNull())
96
+ expect(globalThis.fetch).toHaveBeenCalledWith('https://cdn.web.oslo.kommune.no/header-footer/header-footer.json')
97
+ })
72
98
 
73
- const columnTwoElement = screen.getByText('Column Two')
74
- expect(columnTwoElement).toBeInTheDocument()
99
+ test('rendrer ingenting og kaller onDataError når hentingen feiler', async () => {
100
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('boom', { status: 500 }) as unknown as Response)
101
+ const onDataError = vi.fn()
102
+ const { container } = render(<PktFooter onDataError={onDataError} />)
103
+ await waitFor(() => expect(onDataError).toHaveBeenCalled())
104
+ expect(container.querySelector('.pkt-footer__band--global')).toBeNull()
105
+ expect(container.querySelector('footer')?.textContent?.trim()).toBe('')
106
+ })
107
+ })
75
108
 
76
- const columnTwoLink3Element = screen.getByText('Link 3')
77
- expect(columnTwoLink3Element).toHaveAttribute('href', 'https://example.com')
78
- expect(columnTwoLink3Element).toHaveClass('pkt-link--external')
109
+ describe('PktFooter sekundær footer (custom innhold)', () => {
110
+ test('rendrer kolonner med tittel, tekst og lenker', () => {
111
+ const { container } = render(<PktFooter columns={COLUMNS} data={EXAMPLE_HEADER_FOOTER_DATA} />)
112
+ const columns = [...container.querySelectorAll('.pkt-footer__band--secondary section')]
113
+ expect(columns).toHaveLength(2)
114
+ expect(columns[0].querySelector('h2')?.textContent).toBe('Overskrift 1')
115
+ expect(columns[0].querySelector('p')?.textContent).toContain('lengre tekst')
116
+ expect(columns[1].querySelector('p')).toBeNull()
117
+ })
79
118
 
80
- const columnTwoLink4Element = screen.getByText('Link 4')
81
- expect(columnTwoLink4Element).toHaveAttribute('href', 'https://example.com')
82
- expect(columnTwoLink4Element).not.toHaveClass('pkt-link--external')
119
+ test('eksterne og nye-fane-lenker får riktige attributter', () => {
120
+ render(<PktFooter columns={COLUMNS} data={EXAMPLE_HEADER_FOOTER_DATA} />)
121
+ // external-flagget viser toppdomenet i parentes bak lenketeksten
122
+ const external = screen.getByRole('link', { name: /Lenke 2/ })
123
+ expect(external.querySelector('.pkt-footer__link-domain')?.textContent?.trim()).toBe('(example.test)')
124
+ const newTab = screen.getByRole('link', { name: 'Lenke 3' })
125
+ expect(newTab.querySelector('.pkt-footer__link-domain')).toBeNull()
126
+ expect(newTab).toHaveAttribute('target', '_blank')
127
+ expect(newTab).toHaveAttribute('rel', 'noopener noreferrer')
128
+ })
83
129
 
84
- const columnTwoTextElement = screen.getByText('Column Two Text')
85
- expect(columnTwoTextElement).toBeInTheDocument()
130
+ test('openLinksInNewTab åpner alle custom lenker i ny fane', () => {
131
+ const { container } = render(<PktFooter columns={COLUMNS} data={EXAMPLE_HEADER_FOOTER_DATA} openLinksInNewTab />)
132
+ const links = [...container.querySelectorAll<HTMLAnchorElement>('.pkt-footer__band--secondary a')]
133
+ expect(links.length).toBeGreaterThan(0)
134
+ expect(links.every((l) => l.getAttribute('target') === '_blank')).toBe(true)
135
+ })
86
136
 
87
- const personvernLink = screen.getByText('Personvern og informasjonskapsler')
88
- expect(personvernLink).toBeInTheDocument()
137
+ test('global footer rendres under sekundær footer', () => {
138
+ const { container } = render(<PktFooter columns={COLUMNS} data={EXAMPLE_HEADER_FOOTER_DATA} />)
139
+ const footer = container.querySelector('footer.pkt-footer')
140
+ const children = [...(footer?.children ?? [])].map((c) => c.className)
141
+ expect(children[0]).toContain('pkt-footer__band--secondary')
142
+ expect(children[1]).toContain('pkt-footer__band--global')
143
+ })
89
144
 
90
- const tilgjengelighetLink = screen.getByText('Tilgjengelighet')
91
- expect(tilgjengelighetLink).toBeInTheDocument()
145
+ test('consent rendres i sekundær footer ved skipGlobal + includeConsent', () => {
146
+ const { container } = render(<PktFooter columns={COLUMNS} skipGlobal includeConsent />)
147
+ const consent = container.querySelector('.pkt-footer__consent a.pkt-footer__link')
148
+ expect(consent).not.toBeNull()
149
+ })
150
+ })
92
151
 
93
- const socialLinksElement = screen.getByLabelText('standard lenker')
94
- expect(socialLinksElement).toBeInTheDocument()
152
+ describe('PktFooter deprecated props', () => {
153
+ test('columnOne/columnTwo mappes til kolonner og gir console.warn', () => {
154
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
155
+ const { container } = render(
156
+ <PktFooter
157
+ columnOne={{ title: 'Gammel 1', links: [{ text: 'Lenke', href: '#' }] }}
158
+ columnTwo={{ title: 'Gammel 2' }}
159
+ data={EXAMPLE_HEADER_FOOTER_DATA}
160
+ />,
161
+ )
162
+ const titles = [...container.querySelectorAll('.pkt-footer__band--secondary h2')].map(
163
+ (t) => t.textContent,
164
+ )
165
+ expect(titles).toEqual(['Gammel 1', 'Gammel 2'])
166
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('columnOne'))
167
+ })
95
168
 
96
- const socialLinksFacebookElement = screen.getByLabelText('til facebook')
97
- expect(socialLinksFacebookElement).toHaveAttribute('href', 'https://example.com')
169
+ test('personvern/tilgjengelighet rendres kun ved eksplisitt overstyring', () => {
170
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
171
+ const { container } = render(
172
+ <PktFooter
173
+ columns={COLUMNS}
174
+ personvernOgInfoLink="https://example.test/personvern"
175
+ data={EXAMPLE_HEADER_FOOTER_DATA}
176
+ />,
177
+ )
178
+ const legacyColumn = [...container.querySelectorAll('.pkt-footer__band--secondary section')].find(
179
+ (column) => column.querySelector('h2')?.textContent?.includes('Om nettstedet'),
180
+ )
181
+ expect(legacyColumn?.textContent).toContain('Personvern og informasjonskapsler')
182
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('personvernOgInfoLink'))
183
+ })
98
184
 
99
- const socialLinksTwitterElement = screen.getByLabelText('til twitter')
100
- expect(socialLinksTwitterElement).toHaveAttribute('href', 'https://example.com')
101
- })
185
+ test('default-verdi personvernOgInfoLink rendres ikke', () => {
186
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
187
+ const { container } = render(
188
+ <PktFooter
189
+ columns={COLUMNS}
190
+ personvernOgInfoLink="https://www.oslo.kommune.no/personvern-og-informasjonskapsler/"
191
+ data={EXAMPLE_HEADER_FOOTER_DATA}
192
+ />,
193
+ )
194
+ const titles = [...container.querySelectorAll('.pkt-footer__band--secondary h2')].map(
195
+ (t) => t.textContent,
196
+ )
197
+ expect(titles).not.toContain('Om nettstedet')
102
198
  })
103
199
 
104
- describe('accessibility', () => {
105
- it('renders with no wcag errors with axe', async () => {
106
- const { container } = render(<PktFooter columnOne={columnOne} columnTwo={columnTwo} socialLinks={socialLinks} />)
107
- const results = await axe(container)
108
- expect(results).toHaveNoViolations()
109
- })
200
+ test('socialLinks rendres i sekundær footer med console.warn', () => {
201
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
202
+ const { container } = render(
203
+ <PktFooter
204
+ columns={COLUMNS}
205
+ socialLinks={[
206
+ { href: 'https://example.test/en', language: 'English' },
207
+ { href: 'https://www.facebook.com/Oslo/', iconName: 'facebook' },
208
+ ]}
209
+ data={EXAMPLE_HEADER_FOOTER_DATA}
210
+ />,
211
+ )
212
+ const social = container.querySelector('.pkt-footer__band--secondary .pkt-footer__bottom')
213
+ expect(social?.textContent).toContain('English')
214
+ expect(social?.querySelector('.pkt-footer__some-link')).not.toBeNull()
215
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('socialLinks'))
110
216
  })
111
217
  })