import '@testing-library/jest-dom' import { afterEach, describe, expect, test, vi } from 'vitest' import './footer' import './footer-simple' import { PktFooter } from './footer' import { PktFooterSimple } from './footer-simple' import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer' import type { IPktFooterColumn, IPktFooterLink } from 'shared-types' const COLUMNS: IPktFooterColumn[] = [ { title: 'Overskrift 1', text: 'Her kan du skrive en lengre tekst som beskriver løsningen.', links: [{ text: 'Lenke 1', href: 'https://example.test/lenke-1' }], }, { title: 'Overskrift 2', links: [ { text: 'Lenke 2', href: 'https://example.test/lenke-2', external: true }, { text: 'Lenke 3', href: 'https://example.test/lenke-3', openInNewTab: true }, ], }, ] const SIMPLE_LINKS: IPktFooterLink[] = [ { text: 'Lenke 1', href: 'https://example.test/lenke-1' }, { text: 'Lenke 2', href: 'https://example.test/lenke-2' }, ] const mountFooter = async (props: Record = {}): Promise => { const el = document.createElement('pkt-footer') as PktFooter el.data = EXAMPLE_HEADER_FOOTER_DATA Object.assign(el, props) document.body.appendChild(el) await customElements.whenDefined('pkt-footer') await el.updateComplete await new Promise((resolve) => setTimeout(resolve, 0)) await el.updateComplete return el } const mountSimple = async (props: Record = {}): Promise => { const el = document.createElement('pkt-footer-simple') as PktFooterSimple el.data = EXAMPLE_HEADER_FOOTER_DATA Object.assign(el, props) document.body.appendChild(el) await customElements.whenDefined('pkt-footer-simple') await el.updateComplete await new Promise((resolve) => setTimeout(resolve, 0)) await el.updateComplete return el } afterEach(() => { document.body.innerHTML = '' vi.restoreAllMocks() }) describe('PktFooter global footer', () => { test('rendrer kun global footer uten custom innhold', async () => { const el = await mountFooter() expect(el.querySelector('.pkt-footer__band--secondary')).toBeNull() expect(el.querySelector('.pkt-footer__band--global')).not.toBeNull() const titles = [ ...el.querySelectorAll('.pkt-footer__band--global h2'), ].map((t) => t.textContent) expect(titles).toEqual(['Kontakt', 'Jobb i Oslo kommune', 'Om nettstedet']) }) test('rendrer bunnrad med språklenke og some-ikoner', async () => { const el = await mountFooter() const bottom = el.querySelector('.pkt-footer__band--global .pkt-footer__bottom') expect(bottom?.querySelector('a')?.textContent).toContain('English') const someLinks = [...el.querySelectorAll('.pkt-footer__band--global .pkt-footer__some-link')] expect(someLinks).toHaveLength(3) expect(someLinks[0].getAttribute('aria-label')).toBe('Facebook') expect(someLinks[0].querySelector('pkt-icon')?.getAttribute('name')).toBe('facebook') }) test('merker eksterne lenker med toppdomene i parentes', async () => { const el = await mountFooter() const jobLink = [...el.querySelectorAll('.pkt-footer__band--global a')].find( (link) => link.textContent?.includes('Ledige stillinger'), ) expect(jobLink?.querySelector('.pkt-footer__link-domain')?.textContent).toBe('(webcruiter.com)') const internalLink = [ ...el.querySelectorAll('.pkt-footer__band--global a'), ].find((link) => link.textContent?.includes('Tilgjengelighet')) expect(internalLink?.querySelector('.pkt-footer__link-domain')).toBeNull() }) test('skjuler #cb-trigger-lenken uten includeConsent', async () => { const el = await mountFooter() expect(el.textContent).not.toContain('Innstillinger for informasjonskapsler') expect(el.querySelector('pkt-consent')).toBeNull() }) test('erstatter #cb-trigger med pkt-consent når includeConsent er satt', async () => { const el = await mountFooter({ includeConsent: true }) const consent = el.querySelector('pkt-consent') expect(consent).not.toBeNull() expect(consent?.closest('.pkt-footer__band--global')).not.toBeNull() }) test('bruker valgt locale', async () => { const el = await mountFooter({ locale: 'en-GB' }) const titles = [ ...el.querySelectorAll('.pkt-footer__band--global h2'), ].map((t) => t.textContent) expect(titles).toContain('Contact us') }) test('skipGlobal dropper global footer og henter ikke data', async () => { const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('should not run')) const el = document.createElement('pkt-footer') as PktFooter el.skipGlobal = true el.columns = COLUMNS document.body.appendChild(el) await customElements.whenDefined('pkt-footer') await el.updateComplete expect(el.querySelector('.pkt-footer__band--global')).toBeNull() expect(el.querySelector('.pkt-footer__band--secondary')).not.toBeNull() expect(fetchSpy).not.toHaveBeenCalled() }) }) describe('PktFooter henting av data', () => { test('henter data fra endepunkt når data ikke er satt', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue( new Response(JSON.stringify(EXAMPLE_HEADER_FOOTER_DATA), { status: 200, headers: { 'Content-Type': 'application/json' }, }), ) const el = document.createElement('pkt-footer') as PktFooter document.body.appendChild(el) await customElements.whenDefined('pkt-footer') await new Promise((resolve) => setTimeout(resolve, 0)) await el.updateComplete expect(globalThis.fetch).toHaveBeenCalledWith( 'https://cdn.web.oslo.kommune.no/header-footer/header-footer.json', ) expect(el.querySelector('.pkt-footer__band--global')).not.toBeNull() }) test('rendrer ingenting og emitter data-error når hentingen feiler', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue( new Response('boom', { status: 500 }) as unknown as Response, ) const el = document.createElement('pkt-footer') as PktFooter const events: CustomEvent[] = [] el.addEventListener('data-error', (e) => events.push(e as CustomEvent)) document.body.appendChild(el) await customElements.whenDefined('pkt-footer') await new Promise((resolve) => setTimeout(resolve, 0)) await el.updateComplete expect(events).toHaveLength(1) expect(el.querySelector('.pkt-footer__band--global')).toBeNull() expect(el.querySelector('footer')?.textContent?.trim()).toBe('') }) }) describe('PktFooter sekundær footer (custom innhold)', () => { test('rendrer kolonner med tittel, tekst og lenker', async () => { const el = await mountFooter({ columns: COLUMNS }) const columns = [...el.querySelectorAll('.pkt-footer__band--secondary section')] expect(columns).toHaveLength(2) expect(columns[0].querySelector('h2')?.textContent).toBe('Overskrift 1') expect(columns[0].querySelector('p')?.textContent).toContain( 'lengre tekst', ) expect(columns[1].querySelector('p')).toBeNull() const link = columns[0].querySelector('a') expect(link?.getAttribute('href')).toBe('https://example.test/lenke-1') expect(link?.getAttribute('target')).toBe('_self') }) test('eksterne og nye-fane-lenker får riktige attributter', async () => { const el = await mountFooter({ columns: COLUMNS }) const links = [ ...el.querySelectorAll('.pkt-footer__band--secondary a'), ] // external-flagget viser toppdomenet i parentes bak lenketeksten const external = links.find((l) => l.textContent?.includes('Lenke 2')) expect(external?.querySelector('.pkt-footer__link-domain')?.textContent).toBe('(example.test)') const newTab = links.find((l) => l.textContent?.includes('Lenke 3')) expect(newTab?.querySelector('.pkt-footer__link-domain')).toBeNull() expect(newTab?.getAttribute('target')).toBe('_blank') expect(newTab?.getAttribute('rel')).toBe('noopener noreferrer') }) test('openLinksInNewTab åpner alle custom lenker i ny fane', async () => { const el = await mountFooter({ columns: COLUMNS, openLinksInNewTab: true }) const links = [ ...el.querySelectorAll('.pkt-footer__band--secondary a'), ] expect(links.every((l) => l.getAttribute('target') === '_blank')).toBe(true) }) test('global footer rendres under sekundær footer', async () => { const el = await mountFooter({ columns: COLUMNS }) const footer = el.querySelector('footer.pkt-footer') const children = [...(footer?.children ?? [])].map((c) => c.className) expect(children[0]).toContain('pkt-footer__band--secondary') expect(children[1]).toContain('pkt-footer__band--global') }) test('consent rendres i sekundær footer ved skipGlobal + includeConsent', async () => { const el = await mountFooter({ columns: COLUMNS, skipGlobal: true, includeConsent: true }) const consent = el.querySelector('pkt-consent') expect(consent).not.toBeNull() expect(consent?.closest('.pkt-footer__band--secondary')).not.toBeNull() }) }) describe('PktFooterSimple', () => { test('rendrer kompakt lenkerad med pil-ikoner + global footer', async () => { const el = await mountSimple({ links: SIMPLE_LINKS }) const links = [...el.querySelectorAll('.pkt-footer-simple__link')] expect(links).toHaveLength(2) expect(links[0].querySelector('pkt-icon')?.getAttribute('name')).toBe('arrow-link') expect(el.querySelector('.pkt-footer__band--global')).not.toBeNull() }) test('rendrer kun global footer uten links', async () => { const el = await mountSimple() expect(el.querySelector('.pkt-footer-simple .pkt-footer__band--secondary')).toBeNull() expect(el.querySelector('.pkt-footer__band--global')).not.toBeNull() }) test('consent rendres som listepunkt ved skipGlobal + includeConsent', async () => { const el = await mountSimple({ links: SIMPLE_LINKS, skipGlobal: true, includeConsent: true }) expect(el.querySelector('.pkt-footer__band--global')).toBeNull() const consent = el.querySelector('pkt-consent') expect(consent?.closest('.pkt-footer-simple__list')).not.toBeNull() }) })