@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
465 lines (344 loc) • 17.7 kB
text/typescript
import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { fireEvent } from '@testing-library/dom'
expect.extend(toHaveNoViolations)
import './helptext'
import { PktHelptext } from './helptext'
const waitForCustomElements = async () => {
await customElements.whenDefined('pkt-helptext')
await customElements.whenDefined('pkt-icon')
}
// Helper function to create helptext markup
const createHelptext = async (helptextProps = '', content = '') => {
const container = document.createElement('div')
container.innerHTML = `
<pkt-helptext ${helptextProps}>
${content}
</pkt-helptext>
`
document.body.appendChild(container)
await waitForCustomElements()
return container
}
// Cleanup after each test
afterEach(() => {
document.body.innerHTML = ''
})
describe('PktHelptext', () => {
describe('Rendering and basic functionality', () => {
test('renders without errors', async () => {
const container = await createHelptext()
const helptext = container.querySelector('pkt-helptext') as PktHelptext
expect(helptext).toBeInTheDocument()
await helptext.updateComplete
expect(helptext).toBeTruthy()
})
test('renders with basic helptext', async () => {
const helptextContent = 'This is helpful information'
const container = await createHelptext(`helptext="${helptextContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.helptext).toBe(helptextContent)
const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
expect(helptextContainer).toBeInTheDocument()
expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
expect(helptextDiv).toBeInTheDocument()
expect(helptextDiv?.textContent?.trim()).toContain(helptextContent)
})
test('renders with slot content', async () => {
const slotContent = '<p>Slot helptext content</p>'
const container = await createHelptext('', slotContent)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const contentSlot = helptext.querySelector('.pkt-contents')
expect(contentSlot).toBeInTheDocument()
expect(helptext.textContent).toContain('Slot helptext content')
})
test('renders with dropdown helptext', async () => {
const dropdownContent = 'This is expandable helptext content'
const container = await createHelptext(`helptextDropdown="${dropdownContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.helptextDropdown).toBe(dropdownContent)
const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
expect(expandableContainer).toBeInTheDocument()
const button = expandableContainer?.querySelector('button')
expect(button).toBeInTheDocument()
expect(button).toHaveClass('pkt-link')
const icon = button?.querySelector('pkt-icon')
expect(icon).toBeInTheDocument()
})
})
describe('Properties and attributes', () => {
test('applies default properties correctly', async () => {
const container = await createHelptext()
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.forId).toBeTruthy() // Should have a generated ID
expect(helptext.helptext).toBe('')
expect(helptext.helptextDropdown).toBe('')
expect(helptext.helptextDropdownButton).toBeTruthy() // Should have default from specs
expect(helptext.isHelpTextOpen).toBe(false)
})
test('sets forId correctly', async () => {
const customId = 'custom-helptext-id'
const container = await createHelptext(`forId="${customId}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.forId).toBe(customId)
const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
expect(helptextDiv?.id).toBe(`${customId}-helptext`)
})
test('sets helptext content correctly', async () => {
const helptextContent = 'Custom helptext message'
const container = await createHelptext(`helptext="${helptextContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.helptext).toBe(helptextContent)
expect(helptext.textContent).toContain(helptextContent)
})
test('sets dropdown helptext correctly', async () => {
const dropdownContent = 'Dropdown helptext content'
const container = await createHelptext(`helptextDropdown="${dropdownContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.helptextDropdown).toBe(dropdownContent)
})
test('sets dropdown button text correctly', async () => {
const buttonText = 'Custom button text'
const container = await createHelptext(`helptextDropdownButton="${buttonText}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.helptextDropdownButton).toBe(buttonText)
})
})
describe('Dropdown functionality', () => {
test('does not render dropdown when no dropdown content', async () => {
const container = await createHelptext('helptext="Regular helptext"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
expect(expandableContainer).not.toBeInTheDocument()
})
test('renders dropdown when dropdown content is provided', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const expandableContainer = helptext.querySelector('.pkt-inputwrapper__helptext-expandable')
expect(expandableContainer).toBeInTheDocument()
const button = expandableContainer?.querySelector('button')
expect(button).toBeInTheDocument()
const expandableContent = expandableContainer?.querySelector(
'.pkt-inputwrapper__helptext-expandable-closed',
)
expect(expandableContent).toBeInTheDocument()
})
test('toggles dropdown state on button click', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.isHelpTextOpen).toBe(false)
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
expect(button).toBeInTheDocument()
// Click to open
fireEvent.click(button!)
await helptext.updateComplete
expect(helptext.isHelpTextOpen).toBe(true)
const openContent = helptext.querySelector('.pkt-inputwrapper__helptext-expandable-open')
expect(openContent).toBeInTheDocument()
const closedContent = helptext.querySelector('.pkt-inputwrapper__helptext-expandable-closed')
expect(closedContent).not.toBeInTheDocument()
// Click to close
fireEvent.click(button!)
await helptext.updateComplete
expect(helptext.isHelpTextOpen).toBe(false)
})
test('changes icon when dropdown is toggled', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const icon = helptext.querySelector('pkt-icon')
expect(icon?.getAttribute('name')).toBe('chevron-thin-down')
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
fireEvent.click(button!)
await helptext.updateComplete
expect(icon?.getAttribute('name')).toBe('chevron-thin-up')
})
test('dispatches toggleHelpText event on dropdown toggle', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
let eventFired = false
let eventDetail: any = null
helptext.addEventListener('toggleHelpText', (e: Event) => {
eventFired = true
eventDetail = (e as CustomEvent).detail
})
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
fireEvent.click(button!)
expect(eventFired).toBe(true)
expect(eventDetail).toEqual({ isOpen: true })
})
})
describe('CSS classes and styling', () => {
test('applies correct classes when no content', async () => {
const container = await createHelptext()
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
expect(helptextContainer).toBeInTheDocument()
expect(helptextContainer).not.toHaveClass('pkt-inputwrapper__has-helptext')
})
test('applies correct classes when helptext is provided', async () => {
const container = await createHelptext('helptext="Some helptext"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
})
test('applies correct classes when dropdown is provided', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
})
test('applies correct classes for closed dropdown', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const dropdownContent = helptext.querySelector(
'.pkt-inputwrapper__helptext-expandable .pkt-inputwrapper__helptext',
)
expect(dropdownContent).toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
expect(dropdownContent).not.toHaveClass('pkt-inputwrapper__helptext-expandable-open')
})
test('applies correct classes for open dropdown', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
fireEvent.click(button!)
await helptext.updateComplete
const dropdownContent = helptext.querySelector(
'.pkt-inputwrapper__helptext-expandable .pkt-inputwrapper__helptext',
)
expect(dropdownContent).toHaveClass('pkt-inputwrapper__helptext-expandable-open')
expect(dropdownContent).not.toHaveClass('pkt-inputwrapper__helptext-expandable-closed')
})
})
describe('Content rendering', () => {
test('renders HTML content safely in helptext', async () => {
const htmlContent = '<strong>Important</strong> information'
const container = await createHelptext(`helptext="${htmlContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const strong = helptext.querySelector('strong')
expect(strong).toBeInTheDocument()
expect(strong?.textContent).toBe('Important')
})
test('renders HTML content safely in dropdown', async () => {
const htmlContent = '<em>Emphasized</em> dropdown content'
const container = await createHelptext(`helptextDropdown="${htmlContent}"`)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
fireEvent.click(button!)
await helptext.updateComplete
const em = helptext.querySelector('em')
expect(em).toBeInTheDocument()
expect(em?.textContent).toBe('Emphasized')
})
test('renders both regular and dropdown content simultaneously', async () => {
const regularContent = 'Regular helptext'
const dropdownContent = 'Dropdown helptext'
const container = await createHelptext(
`helptext="${regularContent}" helptextDropdown="${dropdownContent}"`,
)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.textContent).toContain(regularContent)
const button = helptext.querySelector('.pkt-inputwrapper__helptext-expandable button')
fireEvent.click(button!)
await helptext.updateComplete
expect(helptext.textContent).toContain(dropdownContent)
})
})
describe('Slot management', () => {
test('detects slot content via hasSlotContent()', async () => {
const container = await createHelptext('', '<span>Slotted content</span>')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
expect(helptext.hasSlotContent()).toBe(true)
})
test('applies has-helptext class when slots are filled', async () => {
const container = await createHelptext('', '<span>Slotted content</span>')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const helptextContainer = helptext.querySelector('.pkt-inputwrapper__helptext-container')
expect(helptextContainer).toHaveClass('pkt-inputwrapper__has-helptext')
})
})
describe('Accessibility', () => {
test('basic helptext is accessible', async () => {
const container = await createHelptext('helptext="Accessible helptext" forId="test-input"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const results = await axe(container)
expect(results).toHaveNoViolations()
const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
expect(helptextDiv?.id).toBe('test-input-helptext')
})
test('dropdown helptext is accessible', async () => {
const container = await createHelptext(
'helptextDropdown="Dropdown content" helptextDropdownButton="Show more info"',
)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const results = await axe(container)
expect(results).toHaveNoViolations()
const button = helptext.querySelector('button')
expect(button).toBeInTheDocument()
expect(button?.type).toBe('button')
expect(button?.textContent?.trim()).toContain('Show more info')
})
test('dropdown button has proper aria state', async () => {
const container = await createHelptext('helptextDropdown="Dropdown content"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
const button = helptext.querySelector('button')
expect(button).toBeInTheDocument()
// Button should be focusable and have proper role
expect(button?.tagName).toBe('BUTTON')
expect(button?.type).toBe('button')
})
})
describe('Integration', () => {
test('works with input wrapper context', async () => {
const container = await createHelptext('forId="input-123" helptext="Field help information"')
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
// The ID should match what an input would use for aria-describedby
const helptextDiv = helptext.querySelector('.pkt-inputwrapper__helptext')
expect(helptextDiv?.id).toBe('input-123-helptext')
})
test('manages multiple content sources correctly', async () => {
const container = await createHelptext(
'helptext="Property text" helptextDropdown="Dropdown text"',
'<span>Slot text</span>',
)
const helptext = container.querySelector('pkt-helptext') as PktHelptext
await helptext.updateComplete
// All content should be present but in correct locations
expect(helptext.textContent).toContain('Property text')
expect(helptext.textContent).toContain('Slot text')
// Dropdown content should be present but hidden initially
const dropdownContent = helptext.querySelector(
'.pkt-inputwrapper__helptext-expandable-closed',
)
expect(dropdownContent).toBeInTheDocument()
expect(dropdownContent?.textContent?.trim()).toContain('Dropdown text')
})
})
})