UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

405 lines (347 loc) 15.2 kB
import '@testing-library/jest-dom' import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { createElementTest, BaseTestConfig } from '../../tests/test-framework' import { CustomElementFor } from '../../tests/component-registry' import './header-service' // Nested components are imported by header-service itself, but we need to wait for them const waitForNestedElements = async () => { await Promise.all([ customElements.whenDefined('pkt-button'), customElements.whenDefined('pkt-icon'), customElements.whenDefined('pkt-link'), customElements.whenDefined('pkt-textinput'), customElements.whenDefined('pkt-header-user-menu'), ]) } export interface HeaderServiceTestConfig extends BaseTestConfig { 'service-name'?: string 'service-link'?: string 'logo-link'?: string compact?: boolean 'hide-logo'?: boolean position?: 'fixed' | 'relative' 'scroll-behavior'?: 'hide' | 'none' 'show-search'?: boolean 'search-placeholder'?: string 'search-value'?: string 'log-out-button-placement'?: 'userMenu' | 'header' | 'both' | 'none' 'can-change-representation'?: boolean 'opened-menu'?: 'none' | 'slot' | 'search' | 'user' 'mobile-breakpoint'?: number 'tablet-breakpoint'?: number } const createHeaderServiceTest = async (config: HeaderServiceTestConfig = {}) => { const result = await createElementTest< CustomElementFor<'pkt-header-service'>, HeaderServiceTestConfig >('pkt-header-service', config) await waitForNestedElements() await result.element.updateComplete return result } describe('pkt-header-service', () => { beforeEach(() => { // Mock window.matchMedia for responsive behavior Object.defineProperty(window, 'matchMedia', { writable: true, value: vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), })), }) // Mock ResizeObserver global.ResizeObserver = class ResizeObserver { observe() {} unobserve() {} disconnect() {} } // Mock scrollTo window.scrollTo = vi.fn() }) afterEach(() => { document.body.innerHTML = '' }) describe('basic rendering', () => { it('renders with service name', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'My Service' }) const serviceName = element.querySelector('.pkt-header-service__service-name') expect(serviceName).toBeTruthy() expect(serviceName?.textContent).toContain('My Service') }) it('applies compact class when compact attribute is set', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', compact: true }) const header = element.querySelector('.pkt-header-service') expect(header?.classList.contains('pkt-header-service--compact')).toBe(true) }) it('shows logo by default', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const logo = element.querySelector('.pkt-header-service__logo') expect(logo).toBeTruthy() }) it('hides logo when hide-logo attribute is set', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'hide-logo': true, }) const logo = element.querySelector('.pkt-header-service__logo') expect(logo).toBeNull() }) }) describe('logoLink attribute', () => { it('renders logo as link when logo-link is provided', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'logo-link': 'https://oslo.kommune.no', }) const logoLink = element.querySelector('.pkt-header-service__logo a') expect(logoLink).toBeTruthy() expect(logoLink?.getAttribute('href')).toBe('https://oslo.kommune.no') }) it('dispatches logo-click event when logo button is clicked', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const eventSpy = vi.fn() element.addEventListener('logo-click', eventSpy) const logoButton = element.querySelector('.pkt-header-service__logo button') if (logoButton) { logoButton.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true })) expect(eventSpy).toHaveBeenCalled() } }) it('renders logo without link when logo-link is not provided', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const logoLink = element.querySelector('.pkt-header-service__logo a') expect(logoLink).toBeNull() }) }) describe('serviceLink and service-click event', () => { it('renders service name as link when service-link is provided', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'My Service', 'service-link': 'https://example.com', }) // Check that serviceLink property is set correctly expect(element.serviceLink).toBe('https://example.com') // Check that pkt-link element exists (may not fully render in test env) const linkElement = element.querySelector('pkt-link') expect(linkElement).toBeTruthy() }) it('dispatches service-click event when service button is clicked', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'My Service' }) const eventSpy = vi.fn() element.addEventListener('service-click', eventSpy) const serviceButton = element.querySelector('button.pkt-header-service__service-link') if (serviceButton) { serviceButton.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true })) expect(eventSpy).toHaveBeenCalled() } }) it('renders service name as span when service-link is not provided', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'My Service' }) const span = element.querySelector('span.pkt-header-service__service-link') expect(span).toBeTruthy() }) }) describe('position and scrollBehavior attributes', () => { it('applies fixed class by default (position="fixed")', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const header = element.querySelector('.pkt-header-service') expect(header?.classList.contains('pkt-header-service--fixed')).toBe(true) }) it('does not apply fixed class when position="relative"', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', position: 'relative', }) const header = element.querySelector('.pkt-header-service') expect(header?.classList.contains('pkt-header-service--fixed')).toBe(false) }) it('applies scroll-to-hide class by default (scroll-behavior="hide")', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const header = element.querySelector('.pkt-header-service') expect(header?.classList.contains('pkt-header-service--scroll-to-hide')).toBe(true) }) it('does not apply scroll-to-hide class when scroll-behavior="none"', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'scroll-behavior': 'none', }) const header = element.querySelector('.pkt-header-service') expect(header?.classList.contains('pkt-header-service--scroll-to-hide')).toBe(false) }) }) describe('search functionality', () => { it('does not render search input by default', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) const search = element.querySelector('.pkt-header-service__search-input') expect(search).toBeNull() }) it('renders search container when show-search is true', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'show-search': true, }) const searchContainer = element.querySelector('.pkt-header-service__search-container') expect(searchContainer).toBeTruthy() }) it('dispatches search event when Enter is pressed in search input', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'show-search': true, }) const eventSpy = vi.fn() element.addEventListener('search', eventSpy) const searchInput = element.querySelector( '.pkt-header-service__search-input input', ) as HTMLInputElement if (searchInput) { searchInput.value = 'test query' searchInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })) await element.updateComplete expect(eventSpy).toHaveBeenCalled() } }) it('dispatches search-change event when search input value changes', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'show-search': true, }) const eventSpy = vi.fn() element.addEventListener('search-change', eventSpy) const searchInput = element.querySelector( '.pkt-header-service__search-input input', ) as HTMLInputElement if (searchInput) { searchInput.value = 'test' searchInput.dispatchEvent(new Event('input', { bubbles: true })) await element.updateComplete expect(eventSpy).toHaveBeenCalled() } }) }) describe('user menu', () => { it('renders user container when user is provided', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) element.user = { name: 'Aksel Olsen' } await element.updateComplete // Check that user container exists const userContainer = element.querySelector('.pkt-header-service__user-container') expect(userContainer).toBeTruthy() // Check that user property is passed correctly expect(element.user.name).toBe('Aksel Olsen') }) it('passes user data to user menu component', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) element.user = { name: 'Aksel Olsen' } await element.updateComplete // Verify user property is set expect(element.user).toEqual({ name: 'Aksel Olsen' }) }) it('passes representing data to user menu component', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) element.user = { name: 'Aksel' } element.representing = { name: 'Oslo Kommune' } await element.updateComplete // Verify representing property is set expect(element.representing).toEqual({ name: 'Oslo Kommune' }) }) it('passes canChangeRepresentation prop when set', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'can-change-representation': true, }) element.user = { name: 'Aksel' } element.representing = { name: 'Oslo Kommune' } await element.updateComplete // Verify canChangeRepresentation property is set expect(element.canChangeRepresentation).toBe(true) }) }) describe('logout button placement', () => { it('shows logout button in user area when log-out-button-placement="header"', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'log-out-button-placement': 'header', }) element.user = { name: 'Aksel' } await element.updateComplete const userArea = element.querySelector('.pkt-header-service__user') const logoutBtn = userArea?.querySelector('pkt-button') expect(logoutBtn).toBeTruthy() }) it('sets up logout button with correct properties', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'log-out-button-placement': 'header', }) element.user = { name: 'Aksel' } await element.updateComplete // Verify logOutButtonPlacement property is set expect(element.logOutButtonPlacement).toBe('header') // Verify logout button component exists in DOM const logoutBtn = element.querySelector('.pkt-header-service__user pkt-button') expect(logoutBtn).toBeTruthy() }) it('does not show logout button in header areas when log-out-button-placement="userMenu"', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'log-out-button-placement': 'userMenu', }) element.user = { name: 'Aksel' } await element.updateComplete // Logout should not be in header areas const userArea = element.querySelector('.pkt-header-service__user') const contentArea = element.querySelector('.pkt-header-service__content') const userAreaLogout = userArea?.querySelectorAll('pkt-button[icon-name="exit"]') const contentAreaLogout = contentArea?.querySelectorAll('pkt-button[icon-name="exit"]') expect(userAreaLogout?.length || 0).toBe(0) expect(contentAreaLogout?.length || 0).toBe(0) }) }) describe('user menu items', () => { it('passes user menu items to user menu component', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' }) element.user = { name: 'Aksel' } element.userMenu = [ { title: 'Mine bookinger', iconName: 'heart', target: '/bookinger' }, { title: 'Innstillinger', iconName: 'cogwheel', target: () => {} }, ] await element.updateComplete // Verify userMenu property is set correctly expect(element.userMenu).toHaveLength(2) expect(element.userMenu[0].title).toBe('Mine bookinger') expect(element.userMenu[1].title).toBe('Innstillinger') }) }) describe('events', () => { it('dispatches change-representation event when change button is clicked', async () => { const { element } = await createHeaderServiceTest({ 'service-name': 'Svc', 'can-change-representation': true, }) element.user = { name: 'Aksel' } element.representing = { name: 'Oslo Kommune' } await element.updateComplete const eventSpy = vi.fn() element.addEventListener('change-representation', eventSpy) // Open user menu and click change button const userButton = element.querySelector('.pkt-user-menu__button') as HTMLElement userButton?.click() await element.updateComplete const allButtons = element.querySelectorAll('button') const changeButton = Array.from(allButtons || []).find((btn) => btn.textContent?.includes('Endre organisasjon'), ) if (changeButton) { changeButton.click() await element.updateComplete expect(eventSpy).toHaveBeenCalled() } }) }) })