@oslokommune/punkt-elements 16.16.1 → 16.17.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.
- package/CHANGELOG.md +35 -0
- package/dist/{accordion-nNH87Y60.js → accordion-CU2tJ5vn.js} +23 -9
- package/dist/{accordion-BB1JrONI.cjs → accordion-DDZkjXFb.cjs} +20 -7
- package/dist/{checkbox-nTbihMxW.cjs → checkbox-BerAYcfK.cjs} +1 -1
- package/dist/{checkbox-Bjp43NxA.js → checkbox-CF5mSBR5.js} +1 -1
- package/dist/{combobox-C4Q6kQEs.cjs → combobox-DYttdLJ4.cjs} +1 -1
- package/dist/{combobox-CL9BLuxt.js → combobox-DjvRMKOD.js} +3 -3
- package/dist/components/header-menu/header-menu.d.ts +41 -0
- package/dist/components/header-menu/header-menu.test.d.ts +0 -0
- package/dist/components/header-menu/index.d.ts +5 -0
- package/dist/components/header-menu/types.d.ts +6 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/searchinput/index.d.ts +4 -0
- package/dist/components/searchinput/searchinput.d.ts +27 -0
- package/dist/components/searchinput/searchinput.test.d.ts +0 -0
- package/dist/{datepicker-DYd3JCs_.cjs → datepicker-D3QQbd_H.cjs} +1 -1
- package/dist/{datepicker-BUK1Qanw.js → datepicker-M_jcY3wp.js} +2 -2
- package/dist/docs/components/header-full.d.ts +13 -0
- package/dist/docs/components/header-menu.d.ts +8 -0
- package/dist/docs/components/searchinput.d.ts +14 -0
- package/dist/{fileupload-DjUAvBcP.cjs → fileupload-BD_0oMrW.cjs} +1 -1
- package/dist/{fileupload-DbmqRkz3.js → fileupload-F9UMO7eS.js} +3 -3
- package/dist/focus-modality-controller-BSZAQk8y.cjs +1 -0
- package/dist/focus-modality-controller-pzSZkDkH.js +29 -0
- package/dist/{header-Dps8o-ZY.cjs → header-DZranHLm.cjs} +2 -2
- package/dist/{header-T5O6sfCN.js → header-j0SSHF4C.js} +4 -3
- package/dist/header-menu-CPI1_5oZ.cjs +126 -0
- package/dist/header-menu-zhGYdnvz.js +319 -0
- package/dist/header-menu.d.ts +1 -0
- package/dist/{helptext-C5rG4VkA.js → helptext-BdqoW19r.js} +1 -1
- package/dist/{helptext-DQgStO4D.cjs → helptext-KWXjKljV.cjs} +1 -1
- package/dist/{input-element-CjebcQd1.js → input-element-B1ciW1N2.js} +1 -1
- package/dist/{input-element-FGcr5hGd.cjs → input-element-D0egtEnr.cjs} +1 -1
- package/dist/{input-wrapper-B_XFi211.cjs → input-wrapper-DOIWggEv.cjs} +1 -1
- package/dist/{input-wrapper-DBpMVSxt.js → input-wrapper-DVU68NcJ.js} +5 -29
- package/dist/{modal-B6591sWa.cjs → modal-C_LxgxzC.cjs} +1 -1
- package/dist/{modal-DyIC3uhs.js → modal-Ca7dETTw.js} +1 -1
- package/dist/pkt-accordion.cjs +1 -1
- package/dist/pkt-accordion.js +1 -1
- package/dist/pkt-checkbox.cjs +1 -1
- package/dist/pkt-checkbox.js +1 -1
- package/dist/pkt-combobox.cjs +1 -1
- package/dist/pkt-combobox.js +1 -1
- package/dist/pkt-consent.cjs +1 -1
- package/dist/pkt-consent.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +1 -1
- package/dist/pkt-fileupload.cjs +1 -1
- package/dist/pkt-fileupload.js +1 -1
- package/dist/pkt-header-menu.cjs +1 -0
- package/dist/pkt-header-menu.js +2 -0
- package/dist/pkt-header.cjs +1 -1
- package/dist/pkt-header.js +1 -1
- package/dist/pkt-helptext.cjs +1 -1
- package/dist/pkt-helptext.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +24 -22
- package/dist/pkt-input-wrapper.cjs +1 -1
- package/dist/pkt-input-wrapper.js +1 -1
- package/dist/pkt-link.cjs +1 -1
- package/dist/pkt-link.js +1 -1
- package/dist/pkt-linkcard.cjs +1 -1
- package/dist/pkt-linkcard.js +1 -1
- package/dist/pkt-loader.cjs +1 -1
- package/dist/pkt-loader.js +1 -1
- package/dist/pkt-messagebox.cjs +1 -1
- package/dist/pkt-messagebox.js +1 -1
- package/dist/pkt-modal.cjs +1 -1
- package/dist/pkt-modal.js +1 -1
- package/dist/pkt-progressbar.cjs +1 -1
- package/dist/pkt-progressbar.js +1 -1
- package/dist/pkt-radiobutton.cjs +1 -1
- package/dist/pkt-radiobutton.js +1 -1
- package/dist/pkt-searchinput.cjs +1 -0
- package/dist/pkt-searchinput.js +2 -0
- package/dist/pkt-select.cjs +1 -1
- package/dist/pkt-select.js +1 -1
- package/dist/pkt-tabs.cjs +1 -1
- package/dist/pkt-tabs.js +1 -1
- package/dist/pkt-textarea.cjs +1 -1
- package/dist/pkt-textarea.js +1 -1
- package/dist/pkt-textinput.cjs +1 -1
- package/dist/pkt-textinput.js +1 -1
- package/dist/pkt-timepicker.cjs +1 -1
- package/dist/pkt-timepicker.js +1 -1
- package/dist/{radiobutton-DvXr7tDk.cjs → radiobutton-B1Xg6XzP.cjs} +1 -1
- package/dist/{radiobutton-B-8-wWFf.js → radiobutton-BAn1V_mQ.js} +1 -1
- package/dist/searchinput-BVIaoetB.cjs +60 -0
- package/dist/searchinput-CIseS5tL.js +147 -0
- package/dist/searchinput.d.ts +1 -0
- package/dist/{select-D9B8Ighz.js → select-6XCVj1lP.js} +3 -3
- package/dist/{select-B75wzvyz.cjs → select-DO0xGhQQ.cjs} +1 -1
- package/dist/shared-types/header-menu.d.ts +108 -0
- package/dist/shared-types/index.d.ts +3 -0
- package/dist/shared-types/searchinput.d.ts +41 -0
- package/dist/shared-types/skin.d.ts +1 -1
- package/dist/shared-utils/header-menu/example-data.d.ts +11 -0
- package/dist/shared-utils/header-menu/fetch-header-footer-data.d.ts +12 -0
- package/dist/shared-utils/header-menu/icon-map.d.ts +34 -0
- package/dist/shared-utils/header-menu/index.d.ts +11 -0
- package/dist/shared-utils/header-menu/select-megamenu.d.ts +16 -0
- package/dist/tests/component-registry.d.ts +2 -0
- package/dist/{textarea-CjqprL6Y.cjs → textarea-CV6z3LDf.cjs} +1 -1
- package/dist/{textarea-CIWXMaAD.js → textarea-DBSNSvrX.js} +2 -2
- package/dist/{textinput-CxzAxM8E.js → textinput-BZZnFDk3.js} +2 -2
- package/dist/{textinput-B0VWmVIF.cjs → textinput-Dsc-8-BB.cjs} +1 -1
- package/dist/{timepicker-Cv9juQeB.cjs → timepicker-C2Ua6vHY.cjs} +1 -1
- package/dist/{timepicker-DXrpg5C7.js → timepicker-DDQ7CfOy.js} +2 -2
- package/package.json +2 -2
- package/src/components/accordion/accordionitem.ts +22 -5
- package/src/components/header/header-service.ts +1 -0
- package/src/components/header-menu/header-menu.test.ts +314 -0
- package/src/components/header-menu/header-menu.ts +401 -0
- package/src/components/header-menu/index.ts +6 -0
- package/src/components/header-menu/types.ts +31 -0
- package/src/components/index.ts +10 -0
- package/src/components/searchinput/index.ts +10 -0
- package/src/components/searchinput/searchinput.test.ts +312 -0
- package/src/components/searchinput/searchinput.ts +213 -0
- /package/dist/{consent-zh2npq2K.cjs → consent-B5dIDyLD.cjs} +0 -0
- /package/dist/{consent-f_IGyHss.js → consent-DSHrt9eS.js} +0 -0
- /package/dist/{dialog-polyfill.esm-BmVHGRTX.js → dialog-polyfill.esm-B7W3teD0.js} +0 -0
- /package/dist/{dialog-polyfill.esm-CXDsb3l-.cjs → dialog-polyfill.esm-BnAQvMQO.cjs} +0 -0
- /package/dist/{input-wrapper-CYCCx8MP.cjs → input-wrapper-CQGoyLdZ.cjs} +0 -0
- /package/dist/{input-wrapper-kw2y6Jp_.js → input-wrapper-DwSsP39J.js} +0 -0
- /package/dist/{link-B8zYc5WK.cjs → link-DMKjKawh.cjs} +0 -0
- /package/dist/{link-CjJcwcxR.js → link-F51Ho7CV.js} +0 -0
- /package/dist/{linkcard-C4x-NJMi.cjs → linkcard-CBmJbSoV.cjs} +0 -0
- /package/dist/{linkcard-Ddh5R_L-.js → linkcard-E07qJZMP.js} +0 -0
- /package/dist/{loader-CGQXAuyM.cjs → loader-q_eO6zaE.cjs} +0 -0
- /package/dist/{loader-DeizpX45.js → loader-teN65oWo.js} +0 -0
- /package/dist/{messagebox-DqoPqo4K.js → messagebox-Ct9lfe0Z.js} +0 -0
- /package/dist/{messagebox-Ccu29rDv.cjs → messagebox-D1QeQqb8.cjs} +0 -0
- /package/dist/{pkt-options-controller-DbLrdRwH.js → pkt-options-controller-CCB15L_G.js} +0 -0
- /package/dist/{pkt-options-controller-BPBb_O2K.cjs → pkt-options-controller-CeAQ-D-i.cjs} +0 -0
- /package/dist/{progressbar-CrwpnIkv.cjs → progressbar-Ca7pRjmt.cjs} +0 -0
- /package/dist/{progressbar-uC-0W6o4.js → progressbar-o-M17HPA.js} +0 -0
- /package/dist/{tabs-RnDtIFv3.cjs → tabs-CN_RIMWg.cjs} +0 -0
- /package/dist/{tabs-4eWA7CsK.js → tabs-ClI1XQDt.js} +0 -0
|
@@ -4,8 +4,8 @@ import { n as c } from "./element-with-slot-4J2o3SeU.js";
|
|
|
4
4
|
import "./icon-Co72KtgF.js";
|
|
5
5
|
import { t as l } from "./if-defined-Cjj8qN-Z.js";
|
|
6
6
|
import { n as u, t as d } from "./ref-RS8Uv6uC.js";
|
|
7
|
-
import { t as f } from "./input-element-
|
|
8
|
-
import "./input-wrapper-
|
|
7
|
+
import { t as f } from "./input-element-B1ciW1N2.js";
|
|
8
|
+
import "./input-wrapper-DVU68NcJ.js";
|
|
9
9
|
//#region ../../shared-utils/timepicker/time-utils.ts
|
|
10
10
|
var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && parseInt(e.slice(3), 10) <= 59, m = (e) => {
|
|
11
11
|
let [t, n] = e.split(":").map(Number);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-elements",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.17.2",
|
|
4
4
|
"description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
79
79
|
},
|
|
80
80
|
"license": "MIT",
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "3eda56e8d6942a8f8aba63b79a469ac12b375e2d"
|
|
82
82
|
}
|
|
@@ -51,6 +51,8 @@ export class PktAccordionItem extends PktElementWithSlot implements IPktAccordio
|
|
|
51
51
|
[`pkt-accordion-item--${this.skin}`]: !!this.skin,
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const isPlusMinus = this.skin === 'plus-minus'
|
|
55
|
+
|
|
54
56
|
return html`
|
|
55
57
|
<details
|
|
56
58
|
class=${classMap(accordionItemClass)}
|
|
@@ -61,11 +63,26 @@ export class PktAccordionItem extends PktElementWithSlot implements IPktAccordio
|
|
|
61
63
|
>
|
|
62
64
|
<summary class="pkt-accordion-item__title" id=${`pkt-accordion-item-summary-${this.id}`}>
|
|
63
65
|
${this.title}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
${isPlusMinus
|
|
67
|
+
? html`
|
|
68
|
+
<pkt-icon
|
|
69
|
+
name="plus-sign"
|
|
70
|
+
class="pkt-accordion-item__icon pkt-accordion-item__icon--plus"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
></pkt-icon>
|
|
73
|
+
<pkt-icon
|
|
74
|
+
name="minus-sign"
|
|
75
|
+
class="pkt-accordion-item__icon pkt-accordion-item__icon--minus"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
></pkt-icon>
|
|
78
|
+
`
|
|
79
|
+
: html`
|
|
80
|
+
<pkt-icon
|
|
81
|
+
name="chevron-thin-down"
|
|
82
|
+
class="pkt-accordion-item__icon"
|
|
83
|
+
aria-hidden="true"
|
|
84
|
+
></pkt-icon>
|
|
85
|
+
`}
|
|
69
86
|
</summary>
|
|
70
87
|
<div
|
|
71
88
|
class="pkt-accordion-item__content"
|
|
@@ -736,6 +736,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
736
736
|
'pkt-header-service-spacer--compact': this.compact,
|
|
737
737
|
'pkt-header-service-spacer--has-user': !!this.user,
|
|
738
738
|
'pkt-header-service-spacer--mobile': this.isMobile,
|
|
739
|
+
'pkt-header-service-spacer--tablet': this.isTablet,
|
|
739
740
|
})
|
|
740
741
|
|
|
741
742
|
return html`
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
expect.extend(toHaveNoViolations)
|
|
6
|
+
|
|
7
|
+
import './header-menu'
|
|
8
|
+
import { PktHeaderMenu } from './header-menu'
|
|
9
|
+
import {
|
|
10
|
+
deriveSocialIcon,
|
|
11
|
+
ICON_MAP_ODS_TO_PUNKT,
|
|
12
|
+
mapOdsIcon,
|
|
13
|
+
selectLocaleData,
|
|
14
|
+
selectMegamenu,
|
|
15
|
+
} from 'shared-utils/header-menu'
|
|
16
|
+
import type { THeaderFooterApi } from 'shared-types/header-menu'
|
|
17
|
+
|
|
18
|
+
const LOCAL_DATA: THeaderFooterApi = {
|
|
19
|
+
'nb-NO': {
|
|
20
|
+
megamenu: {
|
|
21
|
+
buttons: [{ text: 'Min side', url: 'https://www.oslo.kommune.no/min-side/' }],
|
|
22
|
+
services: {
|
|
23
|
+
title: 'Tjenester og tilbud',
|
|
24
|
+
links: [
|
|
25
|
+
{ icon: '24h', text: 'Døgnåpne tjenester', url: 'https://example.test/24h' },
|
|
26
|
+
{ icon: 'fire-emblem', text: 'Brannvern', url: 'https://example.test/brannvern' },
|
|
27
|
+
{ icon: 'unknown-icon', text: 'Ukjent', url: 'https://example.test/ukjent' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
sections: [
|
|
31
|
+
{
|
|
32
|
+
title: 'Oslo vokser',
|
|
33
|
+
links: [{ text: 'Byutvikling', url: 'https://example.test/byutvikling' }],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: 'Politikk og innsyn',
|
|
37
|
+
links: [{ text: 'Politikk', url: 'https://example.test/politikk' }],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
links: [
|
|
41
|
+
{ text: 'Kontakt', url: 'https://example.test/kontakt' },
|
|
42
|
+
{ text: 'English', url: 'https://example.test/english' },
|
|
43
|
+
],
|
|
44
|
+
some: [
|
|
45
|
+
{ text: 'Facebook', url: 'https://www.facebook.com/Oslo/' },
|
|
46
|
+
{ text: 'LinkedIn', url: 'https://www.linkedin.com/company/oslo-kommune' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
footer: { sections: [], links: [], some: [] },
|
|
50
|
+
i18n: { menu: 'Meny', search: 'Søk', navAriaLabel: 'Oslo kommune hovedmeny' },
|
|
51
|
+
},
|
|
52
|
+
'en-GB': {
|
|
53
|
+
megamenu: {
|
|
54
|
+
services: {
|
|
55
|
+
title: 'Services and information',
|
|
56
|
+
links: [{ icon: '24h', text: '24h', url: 'https://example.test/en/24h' }],
|
|
57
|
+
},
|
|
58
|
+
sections: [],
|
|
59
|
+
links: [],
|
|
60
|
+
some: [],
|
|
61
|
+
},
|
|
62
|
+
footer: { sections: [], links: [], some: [] },
|
|
63
|
+
i18n: { menu: 'Menu', search: 'Search', navAriaLabel: 'Oslo kommune main menu' },
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const waitForReady = async () => {
|
|
68
|
+
await customElements.whenDefined('pkt-header-menu')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const mountWithData = async (props: Record<string, unknown> = {}): Promise<PktHeaderMenu> => {
|
|
72
|
+
const el = document.createElement('pkt-header-menu') as PktHeaderMenu
|
|
73
|
+
el.data = LOCAL_DATA
|
|
74
|
+
el.open = true
|
|
75
|
+
Object.assign(el, props)
|
|
76
|
+
document.body.appendChild(el)
|
|
77
|
+
await waitForReady()
|
|
78
|
+
await el.updateComplete
|
|
79
|
+
// Give nested icons/accordions a tick to settle.
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
81
|
+
await el.updateComplete
|
|
82
|
+
return el
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
document.body.innerHTML = ''
|
|
87
|
+
vi.restoreAllMocks()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('shared-utils: icon-map', () => {
|
|
91
|
+
test('mapOdsIcon translates known legacy names', () => {
|
|
92
|
+
expect(mapOdsIcon('fire-emblem')).toBe('shield-fire')
|
|
93
|
+
expect(mapOdsIcon('document')).toBe('document-plain')
|
|
94
|
+
expect(mapOdsIcon('linked-in')).toBe('linkedin')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('mapOdsIcon passes unknown names through unchanged', () => {
|
|
98
|
+
expect(mapOdsIcon('totally-made-up')).toBe('totally-made-up')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('mapOdsIcon handles empty input', () => {
|
|
102
|
+
expect(mapOdsIcon('')).toBe('')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('ICON_MAP_ODS_TO_PUNKT only stores actual renames', () => {
|
|
106
|
+
for (const [from, to] of Object.entries(ICON_MAP_ODS_TO_PUNKT)) {
|
|
107
|
+
expect(from).not.toBe(to)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('mapOdsIcon resolves the icons the live API ships', () => {
|
|
112
|
+
// Identity passes (already valid Punkt names) and renames both reach
|
|
113
|
+
// a non-empty mapped value.
|
|
114
|
+
const liveIcons = ['24h', 'swingset', 'backpack', 'heart-plus', 'crane', 'fire-emblem']
|
|
115
|
+
for (const icon of liveIcons) {
|
|
116
|
+
expect(mapOdsIcon(icon)).toBeTruthy()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('deriveSocialIcon resolves known hostnames from the URL', () => {
|
|
121
|
+
expect(deriveSocialIcon('https://www.facebook.com/Oslo/')).toBe('facebook')
|
|
122
|
+
expect(deriveSocialIcon('https://www.instagram.com/oslo.kommune/')).toBe('instagram')
|
|
123
|
+
expect(deriveSocialIcon('https://www.linkedin.com/company/oslo-kommune')).toBe('linkedin')
|
|
124
|
+
expect(deriveSocialIcon('https://x.com/oslo_kommune')).toBe('x')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('deriveSocialIcon strips www./m. subdomains', () => {
|
|
128
|
+
expect(deriveSocialIcon('https://m.facebook.com/Oslo/')).toBe('facebook')
|
|
129
|
+
expect(deriveSocialIcon('https://facebook.com/Oslo/')).toBe('facebook')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('deriveSocialIcon ignores localized text when URL resolves', () => {
|
|
133
|
+
expect(
|
|
134
|
+
deriveSocialIcon('https://www.facebook.com/Oslo/', 'Følg oss på Facebook'),
|
|
135
|
+
).toBe('facebook')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('deriveSocialIcon falls back to text when the URL is unknown or invalid', () => {
|
|
139
|
+
expect(deriveSocialIcon('mailto:foo@example.com', 'Facebook')).toBe('facebook')
|
|
140
|
+
expect(deriveSocialIcon('not-a-url', 'LinkedIn')).toBe('linkedin')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('deriveSocialIcon falls back to slugified text for unknown platforms', () => {
|
|
144
|
+
expect(deriveSocialIcon(undefined, 'Threads')).toBe('threads')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('deriveSocialIcon returns undefined when both inputs are empty', () => {
|
|
148
|
+
expect(deriveSocialIcon()).toBeUndefined()
|
|
149
|
+
expect(deriveSocialIcon('', '')).toBeUndefined()
|
|
150
|
+
expect(deriveSocialIcon('not-a-url', ' ')).toBeUndefined()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('shared-utils: select-megamenu', () => {
|
|
155
|
+
test('selectLocaleData returns the requested locale slice', () => {
|
|
156
|
+
expect(selectLocaleData(LOCAL_DATA, 'en-GB')?.i18n?.navAriaLabel).toBe('Oslo kommune main menu')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test('selectLocaleData falls back to nb-NO for missing locales', () => {
|
|
160
|
+
expect(selectLocaleData(LOCAL_DATA, 'fr-FR')?.i18n?.navAriaLabel).toBe('Oslo kommune hovedmeny')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('selectMegamenu returns the megamenu slice for the locale', () => {
|
|
164
|
+
expect(selectMegamenu(LOCAL_DATA, 'nb-NO')?.services.title).toBe('Tjenester og tilbud')
|
|
165
|
+
expect(selectMegamenu(LOCAL_DATA, 'en-GB')?.services.title).toBe('Services and information')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('selectLocaleData returns undefined for missing input', () => {
|
|
169
|
+
expect(selectLocaleData(undefined)).toBeUndefined()
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('PktHeaderMenu rendering', () => {
|
|
174
|
+
test('renders services, sections, and footer when given pre-fetched data', async () => {
|
|
175
|
+
const el = await mountWithData()
|
|
176
|
+
|
|
177
|
+
expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
|
|
178
|
+
'Tjenester og tilbud',
|
|
179
|
+
)
|
|
180
|
+
expect(el.querySelectorAll('.pkt-header-menu__service')).toHaveLength(3)
|
|
181
|
+
expect(el.querySelectorAll('.pkt-header-menu__section')).toHaveLength(2)
|
|
182
|
+
expect(el.querySelectorAll('.pkt-header-menu__footer-link')).toHaveLength(2)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('does not fetch when data prop is supplied', async () => {
|
|
186
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('should not run'))
|
|
187
|
+
await mountWithData()
|
|
188
|
+
expect(fetchSpy).not.toHaveBeenCalled()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('maps ODS icon names on service links', async () => {
|
|
192
|
+
const el = await mountWithData()
|
|
193
|
+
const serviceIcons = el.querySelectorAll<HTMLElement>('.pkt-header-menu__service-icon')
|
|
194
|
+
expect(serviceIcons[0].getAttribute('name')).toBe('24h')
|
|
195
|
+
expect(serviceIcons[1].getAttribute('name')).toBe('shield-fire')
|
|
196
|
+
// Unmapped names pass through
|
|
197
|
+
expect(serviceIcons[2].getAttribute('name')).toBe('unknown-icon')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('uses the locale-specific nav aria-label', async () => {
|
|
201
|
+
const el = await mountWithData()
|
|
202
|
+
const nav = el.querySelector('nav')
|
|
203
|
+
expect(nav?.getAttribute('aria-label')).toBe('Oslo kommune hovedmeny')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('switches to en-GB content when locale changes', async () => {
|
|
207
|
+
const el = await mountWithData()
|
|
208
|
+
el.locale = 'en-GB'
|
|
209
|
+
await el.updateComplete
|
|
210
|
+
const nav = el.querySelector('nav')
|
|
211
|
+
expect(nav?.getAttribute('aria-label')).toBe('Oslo kommune main menu')
|
|
212
|
+
expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
|
|
213
|
+
'Services and information',
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('renders social links with derived aria-labels and icons', async () => {
|
|
218
|
+
const el = await mountWithData()
|
|
219
|
+
const socialLinks = el.querySelectorAll<HTMLElement>('.pkt-header-menu__social-link')
|
|
220
|
+
expect(socialLinks).toHaveLength(2)
|
|
221
|
+
expect(socialLinks[0].getAttribute('aria-label')).toBe('Facebook')
|
|
222
|
+
expect(socialLinks[0].querySelector('pkt-icon')?.getAttribute('name')).toBe('facebook')
|
|
223
|
+
expect(socialLinks[1].getAttribute('aria-label')).toBe('LinkedIn')
|
|
224
|
+
expect(socialLinks[1].querySelector('pkt-icon')?.getAttribute('name')).toBe('linkedin')
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test('reflects the open prop to a host class', async () => {
|
|
228
|
+
const el = await mountWithData()
|
|
229
|
+
expect(el.classList.contains('pkt-header-menu')).toBe(true)
|
|
230
|
+
expect(el.classList.contains('pkt-header-menu--open')).toBe(true)
|
|
231
|
+
el.open = false
|
|
232
|
+
await el.updateComplete
|
|
233
|
+
expect(el.classList.contains('pkt-header-menu--open')).toBe(false)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test('emits data-loaded with the supplied payload', async () => {
|
|
237
|
+
const el = document.createElement('pkt-header-menu') as PktHeaderMenu
|
|
238
|
+
el.data = LOCAL_DATA
|
|
239
|
+
const events: CustomEvent[] = []
|
|
240
|
+
el.addEventListener('data-loaded', (e) => events.push(e as CustomEvent))
|
|
241
|
+
document.body.appendChild(el)
|
|
242
|
+
await waitForReady()
|
|
243
|
+
await el.updateComplete
|
|
244
|
+
expect(events.length).toBeGreaterThanOrEqual(1)
|
|
245
|
+
expect(events[0].detail.data).toBe(LOCAL_DATA)
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe('PktHeaderMenu fetching', () => {
|
|
250
|
+
beforeEach(() => {
|
|
251
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(() =>
|
|
252
|
+
Promise.resolve(
|
|
253
|
+
new Response(JSON.stringify(LOCAL_DATA), {
|
|
254
|
+
status: 200,
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('fetches the default URL on connect when no data is supplied', async () => {
|
|
262
|
+
const el = document.createElement('pkt-header-menu') as PktHeaderMenu
|
|
263
|
+
el.open = true
|
|
264
|
+
document.body.appendChild(el)
|
|
265
|
+
await waitForReady()
|
|
266
|
+
// Wait for the async fetch chain to resolve.
|
|
267
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
268
|
+
await el.updateComplete
|
|
269
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
270
|
+
'https://cdn.web.oslo.kommune.no/header-footer/header-footer.json',
|
|
271
|
+
expect.any(Object),
|
|
272
|
+
)
|
|
273
|
+
expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
|
|
274
|
+
'Tjenester og tilbud',
|
|
275
|
+
)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('honors a custom data-url attribute', async () => {
|
|
279
|
+
const el = document.createElement('pkt-header-menu') as PktHeaderMenu
|
|
280
|
+
el.setAttribute('data-url', 'https://custom.test/payload.json')
|
|
281
|
+
document.body.appendChild(el)
|
|
282
|
+
await waitForReady()
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
284
|
+
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
285
|
+
'https://custom.test/payload.json',
|
|
286
|
+
expect.any(Object),
|
|
287
|
+
)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('emits data-error when the fetch fails', async () => {
|
|
291
|
+
vi.restoreAllMocks()
|
|
292
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
293
|
+
new Response('boom', { status: 500 }) as unknown as Response,
|
|
294
|
+
)
|
|
295
|
+
const el = document.createElement('pkt-header-menu') as PktHeaderMenu
|
|
296
|
+
const events: CustomEvent[] = []
|
|
297
|
+
el.addEventListener('data-error', (e) => events.push(e as CustomEvent))
|
|
298
|
+
document.body.appendChild(el)
|
|
299
|
+
await waitForReady()
|
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
301
|
+
await el.updateComplete
|
|
302
|
+
expect(events.length).toBe(1)
|
|
303
|
+
expect((events[0].detail.error as Error).message).toMatch(/HTTP 500/)
|
|
304
|
+
expect(el.querySelector('.pkt-header-menu__error')).not.toBeNull()
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
describe('PktHeaderMenu accessibility', () => {
|
|
309
|
+
test('has no axe violations with the local-data payload', async () => {
|
|
310
|
+
const el = await mountWithData()
|
|
311
|
+
const results = await axe(el)
|
|
312
|
+
expect(results).toHaveNoViolations()
|
|
313
|
+
})
|
|
314
|
+
})
|