@oslokommune/punkt-elements 16.26.7 → 17.0.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 +110 -0
- package/dist/accordion-CbXDxOho.cjs +47 -0
- package/dist/{accordion-CBdIk9gw.js → accordion-DEUHQ4JD.js} +45 -35
- package/dist/{alert-HHTGY0sp.js → alert-CbtVGrHD.js} +31 -17
- package/dist/{alert-B8dLsiTz.cjs → alert-CkUnbPdA.cjs} +5 -5
- package/dist/base-elements/input-element.d.ts +1 -0
- package/dist/button-Cod17rKo.js +115 -0
- package/dist/{button-B5tQpKeG.cjs → button-gvDXtBMC.cjs} +5 -2
- package/dist/{calendar-DIzGBdbh.cjs → calendar-BVXW8DqC.cjs} +1 -1
- package/dist/{calendar-CjlUA-Lb.js → calendar-CjC4qlh6.js} +1 -1
- package/dist/{checkbox-Bt_sC_90.cjs → checkbox-8uxbeU30.cjs} +1 -1
- package/dist/{checkbox-D03BC2Va.js → checkbox-DJDphhqz.js} +1 -1
- package/dist/{combobox-CoQBTWqL.js → combobox-Du5yr-xM.js} +16 -2
- package/dist/combobox-hKrbYLUe.cjs +136 -0
- package/dist/components/accordion/accordion-context.d.ts +7 -0
- package/dist/components/accordion/accordion.d.ts +2 -0
- package/dist/components/accordion/accordionitem.d.ts +1 -0
- package/dist/components/alert/alert.d.ts +5 -0
- package/dist/components/alert/alert.test.d.ts +1 -0
- package/dist/components/button/button.d.ts +4 -1
- package/dist/components/datepicker/datepicker-base.d.ts +2 -0
- package/dist/components/datepicker/datepicker-utils.d.ts +2 -2
- package/dist/components/footer/footer-base.d.ts +44 -0
- package/dist/components/footer/footer-simple.d.ts +20 -0
- package/dist/components/footer/footer.accessibility.test.d.ts +0 -0
- package/dist/components/footer/footer.d.ts +22 -0
- package/dist/components/footer/footer.test.d.ts +0 -0
- package/dist/components/footer/index.d.ts +3 -0
- package/dist/components/footer/types.d.ts +16 -0
- package/dist/components/header/header-chrome-controller.d.ts +44 -0
- package/dist/components/header/header-global.accessibility.test.d.ts +0 -0
- package/dist/components/header/header-global.d.ts +78 -0
- package/dist/components/header/header-global.test.d.ts +0 -0
- package/dist/components/header/header-service.accessibility.test.d.ts +0 -0
- package/dist/components/header/header-service.d.ts +6 -21
- package/dist/components/header/header-service.test.d.ts +1 -1
- package/dist/components/header/header.d.ts +11 -3
- package/dist/components/header/header.test.d.ts +0 -0
- package/dist/components/header/index.d.ts +2 -1
- package/dist/components/header/types.d.ts +28 -2
- package/dist/components/header-menu/header-menu.d.ts +4 -5
- package/dist/components/header-menu/types.d.ts +1 -1
- package/dist/components/index.d.ts +5 -3
- package/dist/components/input-wrapper/input-wrapper.d.ts +2 -0
- package/dist/components/input-wrapper/input-wrapper.test.d.ts +1 -0
- package/dist/components/searchinput/searchinput.d.ts +5 -0
- package/dist/components/select/index.d.ts +1 -1
- package/dist/components/select/select.d.ts +2 -2
- package/dist/components/select/select.test.d.ts +1 -0
- package/dist/components/textarea/textarea.test.d.ts +2 -0
- package/dist/components/textinput/textinput.test.d.ts +1 -0
- package/dist/components/timepicker/timepicker.d.ts +2 -0
- package/dist/components/timepicker/timepicker.test.d.ts +3 -0
- package/dist/consent--Nw43WFU.cjs +18 -0
- package/dist/{consent-D5sY6JK3.js → consent-B2hSTCGC.js} +14 -7
- package/dist/consume-BiOhrhEy.js +167 -0
- package/dist/consume-DKf5SmP9.cjs +1 -0
- package/dist/{converters-BKGBRBpS.js → converters-BBfsEJV5.js} +25 -25
- package/dist/{converters-9jHlXDyy.cjs → converters-Cd0fPEly.cjs} +1 -1
- package/dist/{datepicker-BE5Ll0W4.js → datepicker--Fq8q-J3.js} +30 -12
- package/dist/{datepicker-x4xM3KQz.cjs → datepicker-CEoBYTBU.cjs} +11 -7
- package/dist/docs/components/alert.d.ts +3 -0
- package/dist/docs/components/footer.d.ts +9 -0
- package/dist/docs/components/form-sizes.d.ts +10 -0
- package/dist/docs/components/header-global-full.d.ts +20 -0
- package/dist/docs/components/header-global.d.ts +14 -0
- package/dist/docs/components/searchinput.d.ts +6 -3
- package/dist/fetch-header-footer-data-Pc32etrk.js +88 -0
- package/dist/fetch-header-footer-data-xohWFmYC.cjs +1 -0
- package/dist/fileupload-CGKUJrlj.cjs +401 -0
- package/dist/{fileupload-2hf8YNhO.js → fileupload-D4gQN64T.js} +156 -176
- package/dist/focus-trap-DG3pEuyJ.js +60 -0
- package/dist/focus-trap-DSv4Ifqm.cjs +1 -0
- package/dist/footer-BIrJeRuZ.js +303 -0
- package/dist/footer-Ber-jqM1.cjs +114 -0
- package/dist/footer.d.ts +1 -0
- package/dist/header-BFk1an4z.cjs +465 -0
- package/dist/header-Deg_LGJo.js +1287 -0
- package/dist/header-menu-BHqK4QA6.cjs +126 -0
- package/dist/{header-menu-C8b5vd3n.js → header-menu-GFtJKM4j.js} +25 -97
- package/dist/{helptext-OfqH5npD.js → helptext-C_brWCIT.js} +1 -1
- package/dist/{helptext-eckq4JEX.cjs → helptext-Cq5rNaAC.cjs} +1 -1
- package/dist/{input-element-ZGOWsvKM.cjs → input-element-B0qLbEdF.cjs} +1 -1
- package/dist/{input-element-BFHpeoV8.js → input-element-C0RnXi-J.js} +5 -2
- package/dist/{input-wrapper-DwwE0JcO.js → input-wrapper-BweP6t3O.js} +12 -0
- package/dist/{input-wrapper-BRjQB4zM.cjs → input-wrapper-DPkkiG7S.cjs} +13 -11
- package/dist/{input-wrapper-DaA98B5l.js → input-wrapper-fWV5P4MN.js} +22 -15
- package/dist/{input-wrapper-LcZjpt0L.cjs → input-wrapper-wh3fWIhk.cjs} +1 -1
- package/dist/modal-CPnFteYS.cjs +35 -0
- package/dist/{modal-C6pScpdG.js → modal-xNK_wpHk.js} +11 -20
- package/dist/pkt-accordion.cjs +1 -1
- package/dist/pkt-accordion.js +1 -1
- package/dist/pkt-alert.cjs +1 -1
- package/dist/pkt-alert.js +1 -1
- package/dist/pkt-button.cjs +1 -1
- package/dist/pkt-button.js +1 -1
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.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-footer.cjs +1 -0
- package/dist/pkt-footer.js +2 -0
- package/dist/pkt-header-menu.cjs +1 -1
- package/dist/pkt-header-menu.js +1 -1
- package/dist/pkt-header.cjs +1 -1
- package/dist/pkt-header.js +2 -2
- 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 +27 -26
- package/dist/pkt-input-wrapper.cjs +1 -1
- package/dist/pkt-input-wrapper.js +1 -1
- package/dist/pkt-modal.cjs +1 -1
- package/dist/pkt-modal.js +1 -1
- package/dist/pkt-radiobutton.cjs +1 -1
- package/dist/pkt-radiobutton.js +1 -1
- package/dist/pkt-searchinput.cjs +1 -1
- package/dist/pkt-searchinput.js +1 -1
- 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-DGO6H2Ki.js → radiobutton-C6htwDrT.js} +1 -1
- package/dist/{radiobutton-DY1WWepJ.cjs → radiobutton-DjWBoSHv.cjs} +1 -1
- package/dist/scroll-lock-BN8lOhV5.cjs +1 -0
- package/dist/scroll-lock-DAbNKAUy.js +18 -0
- package/dist/searchinput-CfUVfpqL.js +156 -0
- package/dist/searchinput-DGjeN1YI.cjs +60 -0
- package/dist/{select-D4_HQFkh.cjs → select-CZvAW7W2.cjs} +2 -1
- package/dist/{select-BwHUIlwQ.js → select-CsBXXbLP.js} +4 -3
- package/dist/setupTests.d.ts +0 -0
- package/dist/shared-types/footer.d.ts +95 -0
- package/dist/shared-types/{header-menu.d.ts → header-footer.d.ts} +51 -4
- package/dist/shared-types/header.d.ts +1 -1
- package/dist/shared-types/index.d.ts +6 -4
- package/dist/shared-types/searchinput.d.ts +2 -0
- package/dist/shared-types/select.d.ts +3 -2
- package/dist/shared-types/size.d.ts +4 -0
- package/dist/shared-types/timepicker.d.ts +0 -6
- package/dist/shared-utils/datepicker-utils.d.ts +5 -4
- package/dist/shared-utils/fileupload/focus-trap.d.ts +4 -10
- package/dist/shared-utils/focus-trap/index.d.ts +22 -0
- package/dist/shared-utils/{header-menu → header-footer}/example-data.d.ts +5 -4
- package/dist/shared-utils/header-footer/external-domain.d.ts +26 -0
- package/dist/shared-utils/{header-menu → header-footer}/fetch-header-footer-data.d.ts +6 -2
- package/dist/shared-utils/{header-menu → header-footer}/index.d.ts +2 -1
- package/dist/shared-utils/{header-menu → header-footer}/select-megamenu.d.ts +1 -1
- package/dist/shared-utils/scroll-lock/index.d.ts +24 -0
- package/dist/tabs-BqrKLCjd.js +214 -0
- package/dist/tabs-q62MK_-r.cjs +40 -0
- package/dist/{textarea-D6M7ZhtA.js → textarea-Cm392CEI.js} +5 -3
- package/dist/{textarea-Bt_Zs8gq.cjs → textarea-sTcZxOIB.cjs} +3 -2
- package/dist/{textinput-EDKjcUdg.cjs → textinput-DqJbTglM.cjs} +3 -2
- package/dist/{textinput-oU2EvFsi.js → textinput-TSPcBcih.js} +5 -3
- package/dist/{timepicker-CgOL2sd9.cjs → timepicker-CqFYMV8q.cjs} +8 -7
- package/dist/{timepicker-_G0V_HV3.js → timepicker-DWbgTDKq.js} +10 -9
- package/package.json +3 -3
- package/src/components/accordion/accordion-context.ts +8 -0
- package/src/components/accordion/accordion.test.ts +44 -0
- package/src/components/accordion/accordion.ts +13 -3
- package/src/components/accordion/accordionitem.ts +9 -1
- package/src/components/alert/alert.test.ts +51 -2
- package/src/components/alert/alert.ts +11 -5
- package/src/components/breadcrumbs/breadcrumbs.test.ts +11 -3
- package/src/components/button/button.test.ts +23 -0
- package/src/components/button/button.ts +28 -3
- package/src/components/combobox/combobox.core.test.ts +20 -0
- package/src/components/combobox/combobox.ts +2 -0
- package/src/components/consent/consent.test.ts +6 -3
- package/src/components/consent/consent.ts +15 -7
- package/src/components/datepicker/datepicker-base.ts +5 -1
- package/src/components/datepicker/datepicker-utils.test.ts +10 -0
- package/src/components/datepicker/datepicker.core.test.ts +17 -0
- package/src/components/datepicker/datepicker.ts +8 -0
- package/src/components/footer/footer-base.ts +238 -0
- package/src/components/footer/footer-simple.ts +71 -0
- package/src/components/footer/footer.accessibility.test.ts +66 -0
- package/src/components/footer/footer.test.ts +238 -0
- package/src/components/footer/footer.ts +81 -0
- package/src/components/footer/index.ts +4 -0
- package/src/components/footer/types.ts +21 -0
- package/src/components/header/header-chrome-controller.ts +123 -0
- package/src/components/header/header-global.accessibility.test.ts +110 -0
- package/src/components/header/header-global.test.ts +329 -0
- package/src/components/header/header-global.ts +648 -0
- package/src/components/header/header-service.accessibility.test.ts +79 -0
- package/src/components/header/header-service.test.ts +32 -12
- package/src/components/header/header-service.ts +38 -162
- package/src/components/header/header-user-menu.ts +0 -1
- package/src/components/header/header.test.ts +84 -0
- package/src/components/header/header.ts +52 -5
- package/src/components/header/index.ts +6 -0
- package/src/components/header/types.ts +38 -0
- package/src/components/header-menu/header-menu.test.ts +131 -10
- package/src/components/header-menu/header-menu.ts +23 -24
- package/src/components/header-menu/types.ts +1 -1
- package/src/components/index.ts +18 -2
- package/src/components/input-wrapper/input-wrapper.test.ts +45 -0
- package/src/components/input-wrapper/input-wrapper.ts +17 -9
- package/src/components/modal/modal.ts +8 -9
- package/src/components/searchinput/searchinput.test.ts +43 -0
- package/src/components/searchinput/searchinput.ts +29 -12
- package/src/components/select/index.ts +1 -1
- package/src/components/select/select.test.ts +28 -0
- package/src/components/select/select.ts +4 -3
- package/src/components/textarea/textarea.test.ts +16 -0
- package/src/components/textarea/textarea.ts +3 -1
- package/src/components/textinput/textinput.test.ts +28 -0
- package/src/components/textinput/textinput.ts +3 -1
- package/src/components/timepicker/timepicker.test.ts +26 -0
- package/src/components/timepicker/timepicker.ts +12 -6
- package/dist/accordion-FISAiAbX.cjs +0 -47
- package/dist/button-C_YzTSuU.js +0 -96
- package/dist/combobox-D6H9rWPV.cjs +0 -135
- package/dist/consent-X3wvhteO.cjs +0 -10
- package/dist/fileupload-BLVrslUX.cjs +0 -401
- package/dist/header-CFrsaq0c.cjs +0 -310
- package/dist/header-D_xvPR4R.js +0 -801
- package/dist/header-menu-DMdP_c6X.cjs +0 -126
- package/dist/modal-DKU7PnjJ.cjs +0 -35
- package/dist/searchinput-CyO5k4Q4.js +0 -148
- package/dist/searchinput-DG6YetL1.cjs +0 -60
- package/dist/shared-utils/modal-scroll-lock.d.ts +0 -10
- package/dist/tabs-CGo0ODPa.cjs +0 -40
- package/dist/tabs-ChdR0D1C.js +0 -379
- /package/dist/shared-utils/{header-menu → header-footer}/icon-map.d.ts +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Booleanish, IPktFooterBase, IPktFooterColumn, IPktFooterLink } from 'shared-types'
|
|
2
|
+
|
|
3
|
+
export type { IPktFooterColumn, IPktFooterLink }
|
|
4
|
+
|
|
5
|
+
/** Booleanish variant of the shared base props for Lit attribute handling. */
|
|
6
|
+
type TFooterBooleanishFields = 'skipGlobal' | 'openLinksInNewTab' | 'includeConsent' | 'devMode'
|
|
7
|
+
|
|
8
|
+
export interface IPktFooterElementBase extends Omit<IPktFooterBase, TFooterBooleanishFields> {
|
|
9
|
+
skipGlobal?: Booleanish
|
|
10
|
+
openLinksInNewTab?: Booleanish
|
|
11
|
+
includeConsent?: Booleanish
|
|
12
|
+
devMode?: Booleanish
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IPktFooter extends IPktFooterElementBase {
|
|
16
|
+
columns?: IPktFooterColumn[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IPktFooterSimple extends IPktFooterElementBase {
|
|
20
|
+
links?: IPktFooterLink[]
|
|
21
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
2
|
+
import {
|
|
3
|
+
createScrollLockToken,
|
|
4
|
+
setScrollLock as applyScrollLock,
|
|
5
|
+
type ScrollLockToken,
|
|
6
|
+
} from 'shared-utils/scroll-lock'
|
|
7
|
+
import type { THeaderPosition, THeaderScrollBehavior } from './types'
|
|
8
|
+
|
|
9
|
+
export interface HeaderChromeOptions {
|
|
10
|
+
mobileBreakpoint: number
|
|
11
|
+
tabletBreakpoint: number
|
|
12
|
+
position: THeaderPosition
|
|
13
|
+
scrollBehavior: THeaderScrollBehavior
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shared header shell logic for `pkt-header-service` and `pkt-header-global`:
|
|
18
|
+
* responsive width measurement (ResizeObserver → `isMobile`/`isTablet`),
|
|
19
|
+
* scroll-to-hide (`isHidden`), body scroll-lock, and focus return when a menu
|
|
20
|
+
* closes.
|
|
21
|
+
*
|
|
22
|
+
* The menu state machine, click-outside handling and the scroll-lock
|
|
23
|
+
* *condition* stay in each element since they differ between the variants.
|
|
24
|
+
*/
|
|
25
|
+
export class HeaderChromeController implements ReactiveController {
|
|
26
|
+
private host: ReactiveControllerHost
|
|
27
|
+
private getOptions: () => HeaderChromeOptions
|
|
28
|
+
|
|
29
|
+
isMobile = false
|
|
30
|
+
isTablet = false
|
|
31
|
+
isHidden = false
|
|
32
|
+
/** When true, scroll-to-hide is paused (e.g. while a menu/overlay is open). */
|
|
33
|
+
pauseScrollHide = false
|
|
34
|
+
|
|
35
|
+
private componentWidth = typeof window !== 'undefined' ? window.innerWidth : 0
|
|
36
|
+
private resizeObserver?: ResizeObserver
|
|
37
|
+
private lastScrollPosition = 0
|
|
38
|
+
private scrollLockToken: ScrollLockToken = createScrollLockToken()
|
|
39
|
+
private lastFocusedElement: HTMLElement | null = null
|
|
40
|
+
private shouldRestoreFocus = false
|
|
41
|
+
|
|
42
|
+
constructor(host: ReactiveControllerHost, getOptions: () => HeaderChromeOptions) {
|
|
43
|
+
this.host = host
|
|
44
|
+
this.getOptions = getOptions
|
|
45
|
+
host.addController(this)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
hostConnected(): void {
|
|
49
|
+
window.addEventListener('scroll', this.handleScroll)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
hostDisconnected(): void {
|
|
53
|
+
this.resizeObserver?.disconnect()
|
|
54
|
+
window.removeEventListener('scroll', this.handleScroll)
|
|
55
|
+
applyScrollLock(this.scrollLockToken, false)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Start measuring the header element's width. Call from `firstUpdated`. */
|
|
59
|
+
observe(element: HTMLElement | undefined): void {
|
|
60
|
+
if (!element) return
|
|
61
|
+
this.componentWidth = element.offsetWidth
|
|
62
|
+
this.refreshBreakpoints()
|
|
63
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
this.componentWidth =
|
|
66
|
+
entry.borderBoxSize && entry.borderBoxSize.length > 0
|
|
67
|
+
? entry.borderBoxSize[0].inlineSize
|
|
68
|
+
: entry.contentRect.width
|
|
69
|
+
this.refreshBreakpoints()
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
this.resizeObserver.observe(element)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Recompute `isMobile`/`isTablet` (e.g. when the breakpoints change). */
|
|
76
|
+
refreshBreakpoints(): void {
|
|
77
|
+
const { mobileBreakpoint, tabletBreakpoint } = this.getOptions()
|
|
78
|
+
const isMobile = this.componentWidth < mobileBreakpoint
|
|
79
|
+
const isTablet = this.componentWidth < tabletBreakpoint
|
|
80
|
+
if (isMobile !== this.isMobile || isTablet !== this.isTablet) {
|
|
81
|
+
this.isMobile = isMobile
|
|
82
|
+
this.isTablet = isTablet
|
|
83
|
+
this.host.requestUpdate()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private handleScroll = (): void => {
|
|
88
|
+
if (this.getOptions().scrollBehavior !== 'hide' || this.pauseScrollHide) return
|
|
89
|
+
const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop
|
|
90
|
+
if (currentScrollPosition < 0) return
|
|
91
|
+
if (Math.abs(currentScrollPosition - this.lastScrollPosition) < 60) return
|
|
92
|
+
const hidden = currentScrollPosition > this.lastScrollPosition
|
|
93
|
+
this.lastScrollPosition = currentScrollPosition
|
|
94
|
+
if (hidden !== this.isHidden) {
|
|
95
|
+
this.isHidden = hidden
|
|
96
|
+
this.host.requestUpdate()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setScrollLock(shouldLock: boolean): void {
|
|
101
|
+
applyScrollLock(this.scrollLockToken, shouldLock)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
captureFocus(): void {
|
|
105
|
+
this.lastFocusedElement = document.activeElement as HTMLElement
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
flagRestoreFocus(): void {
|
|
109
|
+
this.shouldRestoreFocus = true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
restoreFocus(): void {
|
|
113
|
+
if (
|
|
114
|
+
this.shouldRestoreFocus &&
|
|
115
|
+
this.lastFocusedElement &&
|
|
116
|
+
document.contains(this.lastFocusedElement)
|
|
117
|
+
) {
|
|
118
|
+
this.lastFocusedElement.focus()
|
|
119
|
+
}
|
|
120
|
+
this.lastFocusedElement = null
|
|
121
|
+
this.shouldRestoreFocus = false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
import './header-global'
|
|
6
|
+
import { PktHeaderGlobal } from './header-global'
|
|
7
|
+
import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer'
|
|
8
|
+
|
|
9
|
+
expect.extend(toHaveNoViolations)
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
13
|
+
writable: true,
|
|
14
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
15
|
+
matches: false,
|
|
16
|
+
media: query,
|
|
17
|
+
onchange: null,
|
|
18
|
+
addListener: vi.fn(),
|
|
19
|
+
removeListener: vi.fn(),
|
|
20
|
+
addEventListener: vi.fn(),
|
|
21
|
+
removeEventListener: vi.fn(),
|
|
22
|
+
dispatchEvent: vi.fn(),
|
|
23
|
+
})),
|
|
24
|
+
})
|
|
25
|
+
global.ResizeObserver = class ResizeObserver {
|
|
26
|
+
observe() {}
|
|
27
|
+
unobserve() {}
|
|
28
|
+
disconnect() {}
|
|
29
|
+
}
|
|
30
|
+
window.scrollTo = vi.fn()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
document.body.innerHTML = ''
|
|
35
|
+
vi.restoreAllMocks()
|
|
36
|
+
vi.unstubAllGlobals()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// jsdom reports offsetWidth as 0 (compact). Zero the breakpoints to force the
|
|
40
|
+
// full desktop layout; omit to exercise the compact/mobile layout.
|
|
41
|
+
const DESKTOP = { mobileBreakpoint: 0, tabletBreakpoint: 0 }
|
|
42
|
+
|
|
43
|
+
const mount = async (props: Record<string, unknown> = {}): Promise<PktHeaderGlobal> => {
|
|
44
|
+
const el = document.createElement('pkt-header-global') as PktHeaderGlobal
|
|
45
|
+
el.data = EXAMPLE_HEADER_FOOTER_DATA
|
|
46
|
+
el.position = 'relative'
|
|
47
|
+
Object.assign(el, props)
|
|
48
|
+
document.body.appendChild(el)
|
|
49
|
+
await customElements.whenDefined('pkt-header-global')
|
|
50
|
+
await el.updateComplete
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
52
|
+
await el.updateComplete
|
|
53
|
+
return el
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const clickHost = async (el: PktHeaderGlobal, selector: string) => {
|
|
57
|
+
el.querySelector<HTMLElement>(selector)?.click()
|
|
58
|
+
await el.updateComplete
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
60
|
+
await el.updateComplete
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('pkt-header-global – tilgjengelighet', () => {
|
|
64
|
+
test('ingen brudd i standardvisning (desktop)', async () => {
|
|
65
|
+
const el = await mount(DESKTOP)
|
|
66
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('ingen brudd i kompakt visning (mobil)', async () => {
|
|
70
|
+
const el = await mount()
|
|
71
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('ingen brudd med innlogget bruker', async () => {
|
|
75
|
+
const el = await mount({ ...DESKTOP, user: { name: 'Aksel Web Opdahl' } })
|
|
76
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('ingen brudd med åpen megameny', async () => {
|
|
80
|
+
const el = await mount(DESKTOP)
|
|
81
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
82
|
+
expect(await axe(el.parentElement as HTMLElement)).toHaveNoViolations()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('ingen brudd med åpent søke-overlay', async () => {
|
|
86
|
+
const el = await mount()
|
|
87
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
88
|
+
expect(await axe(el.parentElement as HTMLElement)).toHaveNoViolations()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('ingen brudd med åpen brukermeny', async () => {
|
|
92
|
+
const el = await mount({ ...DESKTOP, user: { name: 'Aksel Web Opdahl' } })
|
|
93
|
+
await clickHost(el, '.pkt-header-global__user-button')
|
|
94
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('ingen brudd når menydata feiler', async () => {
|
|
98
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network')))
|
|
99
|
+
const el = document.createElement('pkt-header-global') as PktHeaderGlobal
|
|
100
|
+
el.position = 'relative'
|
|
101
|
+
Object.assign(el, DESKTOP)
|
|
102
|
+
document.body.appendChild(el)
|
|
103
|
+
await customElements.whenDefined('pkt-header-global')
|
|
104
|
+
await el.updateComplete
|
|
105
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
106
|
+
await el.updateComplete
|
|
107
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
108
|
+
expect(await axe(el.parentElement as HTMLElement)).toHaveNoViolations()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import './header-global'
|
|
5
|
+
import { PktHeaderGlobal } from './header-global'
|
|
6
|
+
import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer'
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
10
|
+
writable: true,
|
|
11
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
12
|
+
matches: false,
|
|
13
|
+
media: query,
|
|
14
|
+
onchange: null,
|
|
15
|
+
addListener: vi.fn(),
|
|
16
|
+
removeListener: vi.fn(),
|
|
17
|
+
addEventListener: vi.fn(),
|
|
18
|
+
removeEventListener: vi.fn(),
|
|
19
|
+
dispatchEvent: vi.fn(),
|
|
20
|
+
})),
|
|
21
|
+
})
|
|
22
|
+
global.ResizeObserver = class ResizeObserver {
|
|
23
|
+
observe() {}
|
|
24
|
+
unobserve() {}
|
|
25
|
+
disconnect() {}
|
|
26
|
+
}
|
|
27
|
+
window.scrollTo = vi.fn()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const waitForReady = async () => {
|
|
31
|
+
await customElements.whenDefined('pkt-header-global')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const mount = async (props: Record<string, unknown> = {}): Promise<PktHeaderGlobal> => {
|
|
35
|
+
const el = document.createElement('pkt-header-global') as PktHeaderGlobal
|
|
36
|
+
el.data = EXAMPLE_HEADER_FOOTER_DATA
|
|
37
|
+
el.position = 'relative'
|
|
38
|
+
Object.assign(el, props)
|
|
39
|
+
document.body.appendChild(el)
|
|
40
|
+
await waitForReady()
|
|
41
|
+
await el.updateComplete
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
43
|
+
await el.updateComplete
|
|
44
|
+
return el
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const clickHost = async (el: PktHeaderGlobal, selector: string) => {
|
|
48
|
+
const host = el.querySelector<HTMLElement>(selector)
|
|
49
|
+
host?.click()
|
|
50
|
+
await el.updateComplete
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
52
|
+
await el.updateComplete
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
document.body.innerHTML = ''
|
|
57
|
+
vi.restoreAllMocks()
|
|
58
|
+
vi.unstubAllGlobals()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// jsdom reports offsetWidth as 0, which puts the header in compact mode.
|
|
62
|
+
// Force the desktop (full) layout by zeroing the breakpoints.
|
|
63
|
+
const DESKTOP = { mobileBreakpoint: 0, tabletBreakpoint: 0 }
|
|
64
|
+
|
|
65
|
+
describe('pkt-header-global', () => {
|
|
66
|
+
test('viser logo, søkefelt, kontaktlenke og menyknapp (desktop)', async () => {
|
|
67
|
+
const el = await mount(DESKTOP)
|
|
68
|
+
|
|
69
|
+
expect(el.querySelector('.pkt-header-global__logo pkt-icon')).toBeInTheDocument()
|
|
70
|
+
|
|
71
|
+
const searchInput = el.querySelector<HTMLInputElement>('input[name="q"]')
|
|
72
|
+
expect(searchInput).toBeInTheDocument()
|
|
73
|
+
|
|
74
|
+
const contact = el.querySelector<HTMLAnchorElement>('.pkt-header-global__contact')
|
|
75
|
+
expect(contact).toBeInTheDocument()
|
|
76
|
+
expect(contact).toHaveAttribute('href', 'https://www.oslo.kommune.no/kontakt')
|
|
77
|
+
|
|
78
|
+
expect(el.querySelector('#pkt-header-global-menu-toggle')).toBeInTheDocument()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('skjuler kontaktlenken når bruker er innlogget', async () => {
|
|
82
|
+
const el = await mount({ ...DESKTOP, user: { name: 'Aksel Web Opdahl' } })
|
|
83
|
+
expect(el.querySelector('.pkt-header-global__contact')).not.toBeInTheDocument()
|
|
84
|
+
expect(el.querySelector('.pkt-header-global__user-button')).toBeInTheDocument()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('megameny-overlegget forankres til headerens faktiske posisjon', async () => {
|
|
88
|
+
const el = await mount({ ...DESKTOP, position: 'relative' })
|
|
89
|
+
const header = el.querySelector('.pkt-header-global') as HTMLElement
|
|
90
|
+
// Simulate a header sitting 32px down the page (relative/sticky position).
|
|
91
|
+
header.getBoundingClientRect = () =>
|
|
92
|
+
({ bottom: 120, top: 32, left: 16, right: 1296, width: 1280, height: 88 }) as DOMRect
|
|
93
|
+
|
|
94
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
95
|
+
|
|
96
|
+
const overlay = el.querySelector<HTMLElement>('#pkt-header-global-menu')
|
|
97
|
+
// The CSS adds the design offset (104/88px) on top of this anchor.
|
|
98
|
+
expect(overlay?.style.getPropertyValue('--pkt-header-global-top')).toBe('32px')
|
|
99
|
+
expect(overlay?.style.getPropertyValue('--pkt-header-global-left')).toBe('16px')
|
|
100
|
+
expect(overlay?.style.getPropertyValue('--pkt-header-global-width')).toBe('1280px')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('position="sticky" gir sticky-klasse uten wrapper/spacer', async () => {
|
|
104
|
+
const el = await mount({ ...DESKTOP, position: 'sticky' })
|
|
105
|
+
const header = el.querySelector('.pkt-header-global')
|
|
106
|
+
expect(header?.classList.contains('pkt-header-global--sticky')).toBe(true)
|
|
107
|
+
expect(header?.classList.contains('pkt-header-global--fixed')).toBe(false)
|
|
108
|
+
expect(el.querySelector('.pkt-header-global-wrapper')).toBeNull()
|
|
109
|
+
expect(el.querySelector('.pkt-header-global-spacer')).toBeNull()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('logoen lenker til www.oslo.kommune.no som standard', async () => {
|
|
113
|
+
const el = await mount(DESKTOP)
|
|
114
|
+
const logo = el.querySelector('.pkt-header-global__logo a')
|
|
115
|
+
expect(logo?.getAttribute('href')).toBe('https://www.oslo.kommune.no')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('sender logo-click-event når logoen klikkes', async () => {
|
|
119
|
+
const el = await mount(DESKTOP)
|
|
120
|
+
// preventDefault - jsdom kan ikke navigere
|
|
121
|
+
const eventSpy = vi.fn()
|
|
122
|
+
el.addEventListener('logo-click', (e: Event) => {
|
|
123
|
+
;(e as CustomEvent<{ originalEvent: Event }>).detail.originalEvent.preventDefault()
|
|
124
|
+
eventSpy(e)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
el.querySelector<HTMLElement>('.pkt-header-global__logo a')?.dispatchEvent(
|
|
128
|
+
new MouseEvent('click', { bubbles: true, composed: true, cancelable: true }),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
expect(eventSpy).toHaveBeenCalledTimes(1)
|
|
132
|
+
expect(eventSpy.mock.calls[0][0].detail.originalEvent).toBeInstanceOf(MouseEvent)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('søkefeltet er en GET-form mot søke-endepunktet (desktop)', async () => {
|
|
136
|
+
const el = await mount(DESKTOP)
|
|
137
|
+
const form = el.querySelector<HTMLFormElement>('form[role="search"]')
|
|
138
|
+
expect(form).toBeInTheDocument()
|
|
139
|
+
expect(form).toHaveAttribute('action', 'https://www.oslo.kommune.no/sokeresultat/')
|
|
140
|
+
expect(form).toHaveAttribute('method', 'get')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('mobil: søkeknappen åpner et søkefelt-overlay', async () => {
|
|
144
|
+
// Default breakpoints + jsdom offsetWidth 0 → compact layout.
|
|
145
|
+
const el = await mount()
|
|
146
|
+
// Inline search is replaced by a round toggle; no search field yet.
|
|
147
|
+
expect(el.querySelector('.pkt-header-global__search-panel')).not.toBeInTheDocument()
|
|
148
|
+
expect(el.querySelector('#pkt-header-global-search-toggle')).toBeInTheDocument()
|
|
149
|
+
|
|
150
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
151
|
+
|
|
152
|
+
const panel = el.querySelector('.pkt-header-global__search-panel')
|
|
153
|
+
expect(panel).toBeInTheDocument()
|
|
154
|
+
expect(panel?.querySelector('input[name="q"]')).toBeInTheDocument()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('mobil: åpning av søk gir fokus til søkefeltet', async () => {
|
|
158
|
+
const el = await mount()
|
|
159
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
160
|
+
// Focus is moved on the next animation frame.
|
|
161
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve(null)))
|
|
162
|
+
|
|
163
|
+
const input = el.querySelector<HTMLInputElement>('#pkt-header-global-search-panel input')
|
|
164
|
+
expect(input).toBeTruthy()
|
|
165
|
+
expect(document.activeElement).toBe(input)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('mobil: Tab ut av søke-overlay lukker det og flytter fokus til søkeknappen', async () => {
|
|
169
|
+
const el = await mount()
|
|
170
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
171
|
+
|
|
172
|
+
const panel = el.querySelector<HTMLElement>('#pkt-header-global-search-panel')
|
|
173
|
+
const focusables = panel?.querySelectorAll<HTMLElement>('input, button')
|
|
174
|
+
const last = focusables?.[focusables.length - 1]
|
|
175
|
+
last?.focus()
|
|
176
|
+
|
|
177
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
|
178
|
+
await el.updateComplete
|
|
179
|
+
|
|
180
|
+
expect(el.querySelector('.pkt-header-global__search-panel')).not.toBeInTheDocument()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('mobil: fokus utenfor søke-overlay lukker det', async () => {
|
|
184
|
+
const el = await mount()
|
|
185
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
186
|
+
expect(el.querySelector('.pkt-header-global__search-panel')).toBeInTheDocument()
|
|
187
|
+
|
|
188
|
+
const menuToggle = el.querySelector<HTMLElement>('#pkt-header-global-menu-toggle button')
|
|
189
|
+
menuToggle?.focus()
|
|
190
|
+
menuToggle?.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
|
|
191
|
+
await el.updateComplete
|
|
192
|
+
|
|
193
|
+
expect(el.querySelector('.pkt-header-global__search-panel')).not.toBeInTheDocument()
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('mobil: åpning av søk lukker megamenyen', async () => {
|
|
197
|
+
const el = await mount()
|
|
198
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
199
|
+
expect(el.querySelector('.pkt-header-global__menu')).toBeInTheDocument()
|
|
200
|
+
|
|
201
|
+
await clickHost(el, '#pkt-header-global-search-toggle')
|
|
202
|
+
expect(el.querySelector('.pkt-header-global__menu')).not.toBeInTheDocument()
|
|
203
|
+
expect(el.querySelector('.pkt-header-global__search-panel')).toBeInTheDocument()
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('menyknappen åpner megamenyen', async () => {
|
|
207
|
+
const el = await mount()
|
|
208
|
+
expect(el.querySelector('.pkt-header-global__menu')).not.toBeInTheDocument()
|
|
209
|
+
const innerToggle = el.querySelector('#pkt-header-global-menu-toggle button')
|
|
210
|
+
expect(innerToggle).toHaveAttribute('aria-expanded', 'false')
|
|
211
|
+
|
|
212
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
213
|
+
|
|
214
|
+
const menu = el.querySelector('.pkt-header-global__menu pkt-header-menu')
|
|
215
|
+
expect(menu).toBeInTheDocument()
|
|
216
|
+
expect(menu).toHaveAttribute('open')
|
|
217
|
+
expect(innerToggle).toHaveAttribute('aria-expanded', 'true')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('tastatur: Tab fra menyknappen flytter fokus inn i megamenyen', async () => {
|
|
221
|
+
const el = await mount(DESKTOP)
|
|
222
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
223
|
+
|
|
224
|
+
const toggleButton = el.querySelector<HTMLElement>('#pkt-header-global-menu-toggle button')
|
|
225
|
+
toggleButton?.focus()
|
|
226
|
+
|
|
227
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
|
228
|
+
|
|
229
|
+
const menu = el.querySelector<HTMLElement>('.pkt-header-global__menu')
|
|
230
|
+
const firstFocusable = menu?.querySelector<HTMLElement>('a[href], button')
|
|
231
|
+
expect(firstFocusable).toBeTruthy()
|
|
232
|
+
expect(document.activeElement).toBe(firstFocusable)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test('mobil: Tab fra menyknappen flytter fokus inn i megamenyen (accordion)', async () => {
|
|
236
|
+
// Force the mega menu into its mobile accordion layout.
|
|
237
|
+
;(window.matchMedia as unknown) = vi.fn().mockImplementation((query) => ({
|
|
238
|
+
matches: true,
|
|
239
|
+
media: query,
|
|
240
|
+
onchange: null,
|
|
241
|
+
addListener: vi.fn(),
|
|
242
|
+
removeListener: vi.fn(),
|
|
243
|
+
addEventListener: vi.fn(),
|
|
244
|
+
removeEventListener: vi.fn(),
|
|
245
|
+
dispatchEvent: vi.fn(),
|
|
246
|
+
}))
|
|
247
|
+
const el = await mount()
|
|
248
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
249
|
+
|
|
250
|
+
const toggleButton = el.querySelector<HTMLElement>('#pkt-header-global-menu-toggle button')
|
|
251
|
+
toggleButton?.focus()
|
|
252
|
+
|
|
253
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
|
254
|
+
|
|
255
|
+
const menu = el.querySelector<HTMLElement>('.pkt-header-global__menu')
|
|
256
|
+
const firstSummary = menu?.querySelector('summary')
|
|
257
|
+
expect(firstSummary).toBeTruthy()
|
|
258
|
+
expect(document.activeElement).toBe(firstSummary)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('tastatur: Tab ut av megamenyen lukker den og flytter fokus til menyknappen', async () => {
|
|
262
|
+
const el = await mount(DESKTOP)
|
|
263
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
264
|
+
|
|
265
|
+
const menu = el.querySelector<HTMLElement>('.pkt-header-global__menu')
|
|
266
|
+
const focusables = menu?.querySelectorAll<HTMLElement>('a[href], button')
|
|
267
|
+
const last = focusables?.[focusables.length - 1]
|
|
268
|
+
last?.focus()
|
|
269
|
+
|
|
270
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }))
|
|
271
|
+
await el.updateComplete
|
|
272
|
+
|
|
273
|
+
expect(el.querySelector('.pkt-header-global__menu')).not.toBeInTheDocument()
|
|
274
|
+
const toggleButton = el.querySelector<HTMLElement>('#pkt-header-global-menu-toggle button')
|
|
275
|
+
expect(document.activeElement).toBe(toggleButton)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('tastatur: fokus utenfor megamenyen lukker den', async () => {
|
|
279
|
+
const el = await mount(DESKTOP)
|
|
280
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
281
|
+
expect(el.querySelector('.pkt-header-global__menu')).toBeInTheDocument()
|
|
282
|
+
|
|
283
|
+
const contact = el.querySelector<HTMLElement>('.pkt-header-global__contact')
|
|
284
|
+
contact?.focus()
|
|
285
|
+
contact?.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
|
|
286
|
+
await el.updateComplete
|
|
287
|
+
|
|
288
|
+
expect(el.querySelector('.pkt-header-global__menu')).not.toBeInTheDocument()
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('brukerknappen åpner brukermenyen', async () => {
|
|
292
|
+
const el = await mount({ user: { name: 'Aksel Web Opdahl' } })
|
|
293
|
+
expect(el.querySelector('pkt-header-user-menu')).not.toBeInTheDocument()
|
|
294
|
+
|
|
295
|
+
await clickHost(el, '.pkt-header-global__user-button')
|
|
296
|
+
|
|
297
|
+
expect(el.querySelector('pkt-header-user-menu')).toBeInTheDocument()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('viser feilmelding i menyen når data ikke kan lastes', async () => {
|
|
301
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network')))
|
|
302
|
+
const el = document.createElement('pkt-header-global') as PktHeaderGlobal
|
|
303
|
+
el.position = 'relative'
|
|
304
|
+
Object.assign(el, DESKTOP)
|
|
305
|
+
document.body.appendChild(el)
|
|
306
|
+
await waitForReady()
|
|
307
|
+
await el.updateComplete
|
|
308
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
309
|
+
await el.updateComplete
|
|
310
|
+
|
|
311
|
+
await clickHost(el, '#pkt-header-global-menu-toggle')
|
|
312
|
+
|
|
313
|
+
const error = el.querySelector('.pkt-header-menu__error')
|
|
314
|
+
expect(error).toBeInTheDocument()
|
|
315
|
+
expect(error?.textContent).toContain('Kunne ikke laste meny.')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
test('sender data-loaded når data er forhåndshentet', async () => {
|
|
319
|
+
const onLoaded = vi.fn()
|
|
320
|
+
const el = document.createElement('pkt-header-global') as PktHeaderGlobal
|
|
321
|
+
el.addEventListener('data-loaded', onLoaded)
|
|
322
|
+
el.data = EXAMPLE_HEADER_FOOTER_DATA
|
|
323
|
+
el.position = 'relative'
|
|
324
|
+
document.body.appendChild(el)
|
|
325
|
+
await waitForReady()
|
|
326
|
+
await el.updateComplete
|
|
327
|
+
expect(onLoaded).toHaveBeenCalled()
|
|
328
|
+
})
|
|
329
|
+
})
|