@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
365 lines (284 loc) • 11.9 kB
text/typescript
import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import { PktLinkCard, type TLinkCardSkin } from './linkcard'
import type { IPktLinkCard } from './linkcard'
import './linkcard'
export interface LinkCardTestConfig extends IPktLinkCard, BaseTestConfig {}
// Use shared framework
export const createLinkCardTest = async (config: LinkCardTestConfig = {}) => {
const { container, element } = await createElementTest<
CustomElementFor<'pkt-linkcard'>,
LinkCardTestConfig
>('pkt-linkcard', config)
const link = element.querySelector('a') as HTMLAnchorElement
return {
container,
linkCard: element,
link,
}
}
expect.extend(toHaveNoViolations)
// Test data constants
const VALID_SKINS: TLinkCardSkin[] = [
'normal',
'no-padding',
'blue',
'beige',
'green',
'gray',
'beige-outline',
'gray-outline',
]
const SAMPLE_ICONS = ['arrow-right', 'external-link', 'download', 'info']
// Cleanup after each test
afterEach(() => {
document.body.innerHTML = ''
})
// ----------- THE TESTS -----------
describe('PktLinkCard', () => {
describe('Basic Rendering', () => {
test('renders without errors', async () => {
const { linkCard, link } = await createLinkCardTest()
expect(linkCard).toBeInTheDocument()
expect(link).toBeInTheDocument()
expect(link).toHaveClass('pkt-linkcard')
})
test('renders with default properties', async () => {
const { linkCard, link } = await createLinkCardTest()
expect(linkCard.title).toBe('')
expect(linkCard.href).toBe('#')
expect(linkCard.iconName).toBe('')
expect(linkCard.openInNewTab).toBe(false)
expect(linkCard.skin).toBe('normal')
expect(linkCard.external).toBe(false)
expect(link.getAttribute('href')).toBe('#')
expect(link.getAttribute('target')).toBe('_self')
// When openInNewTab is false, rel should be null (ifDefined behavior)
expect(link.getAttribute('rel')).toBeNull()
})
test('renders content in slot', async () => {
const content = '<p>Custom link card content</p>'
const { linkCard } = await createLinkCardTest({ content })
const textSlot = linkCard.querySelector('.pkt-linkcard__text')
expect(textSlot).toBeInTheDocument()
expect(textSlot?.innerHTML).toContain(content)
})
})
describe('Title Functionality', () => {
test('renders title when provided', async () => {
const title = 'Test Link Card Title'
const { linkCard } = await createLinkCardTest({ title })
expect(linkCard.title).toBe(title)
const titleElement = linkCard.querySelector('.pkt-linkcard__title')
expect(titleElement).toBeInTheDocument()
expect(titleElement?.textContent).toBe(title)
})
test('does not render title when not provided', async () => {
const { linkCard } = await createLinkCardTest()
const titleElement = linkCard.querySelector('.pkt-linkcard__title')
expect(titleElement).not.toBeInTheDocument()
})
test('applies correct title classes', async () => {
const title = 'Test Title'
const { linkCard } = await createLinkCardTest({ title })
const titleElement = linkCard.querySelector('.pkt-linkcard__title')
expect(titleElement).toHaveClass('pkt-linkcard__title')
})
})
describe('Link Functionality', () => {
test('renders with custom href', async () => {
const href = '/custom-path'
const { linkCard, link } = await createLinkCardTest({ href })
expect(linkCard.href).toBe(href)
expect(link.getAttribute('href')).toBe(href)
})
test('handles openInNewTab correctly', async () => {
const { linkCard, link } = await createLinkCardTest({ openInNewTab: true })
expect(linkCard.openInNewTab).toBe(true)
expect(link.getAttribute('target')).toBe('_blank')
})
test('handles openInNewTab false correctly', async () => {
const { linkCard, link } = await createLinkCardTest({ openInNewTab: false })
expect(linkCard.openInNewTab).toBe(false)
expect(link.getAttribute('target')).toBe('_self')
})
test('applies correct rel attribute for new tab', async () => {
const { link } = await createLinkCardTest({ openInNewTab: true })
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
})
})
describe('Icon Functionality', () => {
test('renders icon when iconName provided', async () => {
const iconName = 'arrow-right'
const { linkCard } = await createLinkCardTest({ iconName })
expect(linkCard.iconName).toBe(iconName)
const icon = linkCard.querySelector('pkt-icon')
expect(icon).toBeInTheDocument()
expect(icon).toHaveClass('pkt-link__icon')
expect(icon?.getAttribute('name')).toBe(iconName)
})
test('does not render icon when iconName not provided', async () => {
const { linkCard } = await createLinkCardTest()
const icon = linkCard.querySelector('pkt-icon')
expect(icon).not.toBeInTheDocument()
})
test('renders with different icon names', async () => {
for (const iconName of SAMPLE_ICONS) {
const { linkCard } = await createLinkCardTest({ iconName })
const icon = linkCard.querySelector('pkt-icon')
expect(icon?.getAttribute('name')).toBe(iconName)
}
})
})
describe('Skin Variations', () => {
test('applies default skin class', async () => {
const { link } = await createLinkCardTest()
expect(link).toHaveClass('pkt-linkcard')
expect(link).toHaveClass('pkt-linkcard--normal')
})
test('applies different skin classes correctly', async () => {
for (const skin of VALID_SKINS) {
const { link } = await createLinkCardTest({ skin })
expect(link).toHaveClass('pkt-linkcard')
expect(link).toHaveClass(`pkt-linkcard--${skin}`)
}
})
test('handles skin property changes', async () => {
const { linkCard, link } = await createLinkCardTest({ skin: 'blue' })
expect(link).toHaveClass('pkt-linkcard--blue')
// Change skin
linkCard.skin = 'green'
await linkCard.updateComplete
expect(link).toHaveClass('pkt-linkcard--green')
expect(link).not.toHaveClass('pkt-linkcard--blue')
})
})
describe('Property Updates', () => {
test('updates title dynamically', async () => {
const { linkCard } = await createLinkCardTest({ title: 'Initial Title' })
expect(linkCard.querySelector('.pkt-linkcard__title')?.textContent).toBe('Initial Title')
linkCard.title = 'Updated Title'
await linkCard.updateComplete
expect(linkCard.querySelector('.pkt-linkcard__title')?.textContent).toBe('Updated Title')
})
test('updates href dynamically', async () => {
const { linkCard, link } = await createLinkCardTest({ href: '/initial' })
expect(link.getAttribute('href')).toBe('/initial')
linkCard.href = '/updated'
await linkCard.updateComplete
expect(link.getAttribute('href')).toBe('/updated')
})
test('updates openInNewTab dynamically', async () => {
const { linkCard, link } = await createLinkCardTest({ openInNewTab: false })
expect(link.getAttribute('target')).toBe('_self')
linkCard.openInNewTab = true
await linkCard.updateComplete
expect(link.getAttribute('target')).toBe('_blank')
})
})
describe('Complex Configurations', () => {
test('renders with all properties set', async () => {
const config: LinkCardTestConfig = {
title: 'Complete Link Card',
href: '/complete-path',
iconName: 'arrow-right',
openInNewTab: true,
skin: 'blue',
content: '<p>Complete content</p>',
}
const { linkCard, link } = await createLinkCardTest(config)
// Verify all properties
expect(linkCard.title).toBe(config.title)
expect(linkCard.href).toBe(config.href)
expect(linkCard.iconName).toBe(config.iconName)
expect(linkCard.openInNewTab).toBe(config.openInNewTab)
expect(linkCard.skin).toBe(config.skin)
// Verify DOM elements
expect(link.getAttribute('href')).toBe(config.href)
expect(link.getAttribute('target')).toBe('_blank')
expect(link).toHaveClass(`pkt-linkcard--${config.skin}`)
const titleElement = linkCard.querySelector('.pkt-linkcard__title')
expect(titleElement?.textContent).toBe(config.title)
const icon = linkCard.querySelector('pkt-icon')
expect(icon?.getAttribute('name')).toBe(config.iconName)
const textSlot = linkCard.querySelector('.pkt-linkcard__text')
expect(textSlot?.innerHTML).toContain(config.content)
})
test('renders minimal configuration', async () => {
const { linkCard, link } = await createLinkCardTest({ href: '/minimal' })
expect(linkCard.href).toBe('/minimal')
expect(link.getAttribute('href')).toBe('/minimal')
expect(link).toHaveClass('pkt-linkcard--normal')
// Should not have title or icon
expect(linkCard.querySelector('.pkt-linkcard__title')).not.toBeInTheDocument()
expect(linkCard.querySelector('pkt-icon')).not.toBeInTheDocument()
})
})
describe('Edge Cases', () => {
test('handles empty string values', async () => {
const { linkCard } = await createLinkCardTest({
title: '',
href: '',
iconName: '',
})
expect(linkCard.title).toBe('')
expect(linkCard.href).toBe('')
expect(linkCard.iconName).toBe('')
// Should not render title or icon with empty strings
expect(linkCard.querySelector('.pkt-linkcard__title')).not.toBeInTheDocument()
expect(linkCard.querySelector('pkt-icon')).not.toBeInTheDocument()
})
test('handles special characters in title', async () => {
const specialTitle = 'Title with "quotes" & <symbols>'
const { linkCard } = await createLinkCardTest()
// Set title via property to avoid HTML attribute encoding issues
linkCard.title = specialTitle
await linkCard.updateComplete
expect(linkCard.title).toBe(specialTitle)
const titleElement = linkCard.querySelector('.pkt-linkcard__title')
expect(titleElement).toBeInTheDocument()
expect(titleElement?.textContent).toBe(specialTitle)
})
})
describe('Accessibility', () => {
test('has no accessibility violations', async () => {
const { linkCard } = await createLinkCardTest({
title: 'Accessible Link Card',
href: '/accessible',
})
const results = await axe(linkCard)
expect(results).toHaveNoViolations()
})
test('maintains proper link semantics', async () => {
const { link } = await createLinkCardTest({
title: 'Semantic Link',
href: '/semantic',
})
expect(link.tagName).toBe('A')
expect(link.getAttribute('href')).toBe('/semantic')
expect(link.textContent).toContain('Semantic Link')
})
test('handles external link accessibility', async () => {
const { link } = await createLinkCardTest({
title: 'External Link',
href: 'https://example.com',
openInNewTab: true,
})
expect(link.getAttribute('target')).toBe('_blank')
expect(link.getAttribute('rel')).toBe('noopener noreferrer')
})
})
describe('Type Safety', () => {
test('validates interface implementation', () => {
const linkCard = new PktLinkCard()
// Check that all interface properties exist
expect(linkCard).toHaveProperty('title')
expect(linkCard).toHaveProperty('href')
expect(linkCard).toHaveProperty('iconName')
expect(linkCard).toHaveProperty('openInNewTab')
expect(linkCard).toHaveProperty('skin')
})
})
})