import '@testing-library/jest-dom'
import { cleanup, render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'
import { createRef } from 'react'
import { PktCard } from './Card'
expect.extend(toHaveNoViolations)
afterEach(cleanup)
describe('PktCard', () => {
describe('Rendering and basic functionality', () => {
test('renders without errors', () => {
const { container } = render(innhold)
const article = container.querySelector('article.pkt-card')
expect(article).toBeInTheDocument()
})
test('renders content in children', () => {
const { container } = render(Test content here
)
const content = container.querySelector('.pkt-card__content')
expect(content).toBeInTheDocument()
expect(content?.textContent).toContain('Test content here')
})
test('renders basic structure correctly', () => {
const { container } = render(Test content)
const article = container.querySelector('article')
const wrapper = article?.querySelector('.pkt-card__wrapper')
const header = wrapper?.querySelector('.pkt-card__header')
const content = wrapper?.querySelector('.pkt-card__content')
expect(wrapper).toBeInTheDocument()
expect(header).toBeInTheDocument()
expect(content).toBeInTheDocument()
})
test('forwards ref correctly', () => {
const ref = createRef()
const { unmount } = render(innhold)
expect(ref.current).toBeInstanceOf(HTMLElement)
unmount()
expect(ref.current).toBeNull()
})
})
describe('Properties and attributes', () => {
test('applies default properties correctly', () => {
const { container } = render(innhold)
const article = container.querySelector('article')
expect(article).toHaveClass('pkt-card--outlined')
expect(article).toHaveClass('pkt-card--vertical')
expect(article).toHaveClass('pkt-card--padding-default')
expect(article).toHaveClass('pkt-card--border-on-hover')
})
test('applies different skin properties correctly', () => {
const skins = ['outlined', 'outlined-beige', 'gray', 'beige', 'green', 'blue'] as const
for (const skin of skins) {
const { container, unmount } = render(innhold)
const article = container.querySelector('article')
expect(article).toHaveClass(`pkt-card--${skin}`)
unmount()
}
})
test('applies different layout properties correctly', () => {
const layouts = ['vertical', 'horizontal'] as const
for (const layout of layouts) {
const { container, unmount } = render(innhold)
const article = container.querySelector('article')
expect(article).toHaveClass(`pkt-card--${layout}`)
unmount()
}
})
test('applies different padding properties correctly', () => {
const paddingOptions = ['none', 'default'] as const
for (const padding of paddingOptions) {
const { container, unmount } = render(innhold)
const article = container.querySelector('article')
expect(article).toHaveClass(`pkt-card--padding-${padding}`)
unmount()
}
})
test('handles borderOnHover property correctly', () => {
const { container } = render(innhold)
const article = container.querySelector('article')
expect(article).not.toHaveClass('pkt-card--border-on-hover')
})
test('supports custom className', () => {
const { container } = render(innhold)
const wrapper = container.querySelector('.pkt-card-root')
expect(wrapper).toHaveClass('custom-class')
})
})
describe('Heading functionality', () => {
test('renders heading when provided', () => {
const { container } = render(innhold)
const heading = container.querySelector('.pkt-card__heading')
expect(heading).toBeInTheDocument()
expect(heading?.textContent?.trim()).toBe('Test Card Title')
})
test('renders subheading when provided', () => {
const { container } = render(innhold)
const subheading = container.querySelector('.pkt-card__subheading')
expect(subheading).toBeInTheDocument()
expect(subheading?.textContent).toBe('Test Subheading')
})
test('applies correct heading level', () => {
const { container } = render(
innhold,
)
const heading = container.querySelector('h2.pkt-card__heading')
expect(heading).toBeInTheDocument()
})
test('defaults to h3 heading level', () => {
const { container } = render(innhold)
const heading = container.querySelector('h3.pkt-card__heading')
expect(heading).toBeInTheDocument()
})
test('does not render header when no heading or subheading', () => {
const { container } = render(innhold)
const header = container.querySelector('.pkt-card__header')
expect(header).not.toBeInTheDocument()
})
})
describe('Link functionality', () => {
test('renders as regular card when no clickCardLink', () => {
const { container } = render(innhold)
const link = container.querySelector('.pkt-card__link')
expect(link).not.toBeInTheDocument()
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('Test Title')
})
test('renders as link card when clickCardLink provided', () => {
const { container } = render(
innhold,
)
const linkHeading = container.querySelector('.pkt-card__link-heading')
const link = container.querySelector('.pkt-card__link')
expect(linkHeading).toBeInTheDocument()
expect(link).toBeInTheDocument()
expect(link?.getAttribute('href')).toBe('/test-url')
expect(link?.textContent).toBe('Test Title')
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('Test Title lenkekort')
})
test('does not render plain heading when clickCardLink is set', () => {
const { container } = render(
innhold,
)
const headings = container.querySelectorAll('.pkt-card__heading')
// Should only have the link heading, not both
expect(headings).toHaveLength(1)
expect(headings[0]).toHaveClass('pkt-card__link-heading')
})
test('handles openLinkInNewTab correctly', () => {
const { container } = render(
innhold,
)
const link = container.querySelector('.pkt-card__link')
expect(link?.getAttribute('target')).toBe('_blank')
expect(link?.getAttribute('rel')).toBe('noopener noreferrer')
})
test('applies correct aria-label for link cards with custom ariaLabel', () => {
const { container } = render(
innhold,
)
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('Custom aria label')
})
})
describe('Image functionality', () => {
test('renders image when provided', () => {
const { container } = render(
innhold,
)
const imageDiv = container.querySelector('.pkt-card__image')
const img = imageDiv?.querySelector('img')
expect(imageDiv).toBeInTheDocument()
expect(img).toBeInTheDocument()
expect(img?.getAttribute('src')).toBe('/test-image.jpg')
expect(img?.getAttribute('alt')).toBe('Test image')
})
test('does not render image when not provided', () => {
const { container } = render(innhold)
const imageDiv = container.querySelector('.pkt-card__image')
expect(imageDiv).not.toBeInTheDocument()
})
test('applies correct image shape classes', () => {
const shapes = ['square', 'round'] as const
for (const shape of shapes) {
const { container, unmount } = render(
innhold,
)
const imageDiv = container.querySelector('.pkt-card__image')
expect(imageDiv).toHaveClass(`pkt-card__image-${shape}`)
unmount()
}
})
})
describe('Tags functionality', () => {
test('renders tags when provided', () => {
const tags = [
{ text: 'Tag 1', skin: 'blue' as const },
{ text: 'Tag 2', skin: 'green' as const },
]
const { container } = render(innhold)
const tagsContainer = container.querySelector('.pkt-card__tags')
expect(tagsContainer).toBeInTheDocument()
expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapper')
})
test('renders single tag with correct aria-label', () => {
const tags = [{ text: 'Single Tag' }]
const { container } = render(innhold)
const tagsContainer = container.querySelector('.pkt-card__tags')
expect(tagsContainer?.getAttribute('aria-label')).toBe('merkelapp')
})
test('applies correct tag position classes', () => {
const positions = ['top', 'bottom'] as const
for (const position of positions) {
const tags = [{ text: 'Test Tag' }]
const { container, unmount } = render(
innhold,
)
const tagsContainer = container.querySelector('.pkt-card__tags')
expect(tagsContainer).toHaveClass(`pkt-card__tags-${position}`)
unmount()
}
})
test('does not render tags when array is empty', () => {
const { container } = render(innhold)
const tagsContainer = container.querySelector('.pkt-card__tags')
expect(tagsContainer).not.toBeInTheDocument()
})
})
describe('Metadata functionality', () => {
test('renders metadata when provided', () => {
const { container } = render(
innhold,
)
const metadata = container.querySelector('.pkt-card__metadata')
const metaLead = metadata?.querySelector('.pkt-card__metadata-lead')
const metaTrail = metadata?.querySelector('.pkt-card__metadata-trail')
expect(metadata).toBeInTheDocument()
expect(metaLead?.textContent).toBe('Author Name')
expect(metaTrail?.textContent).toBe('2023-12-01')
})
test('renders only metaLead when metaTrail not provided', () => {
const { container } = render(innhold)
const metaLead = container.querySelector('.pkt-card__metadata-lead')
const metaTrail = container.querySelector('.pkt-card__metadata-trail')
expect(metaLead).toBeInTheDocument()
expect(metaTrail).not.toBeInTheDocument()
})
test('renders only metaTrail when metaLead not provided', () => {
const { container } = render(innhold)
const metaLead = container.querySelector('.pkt-card__metadata-lead')
const metaTrail = container.querySelector('.pkt-card__metadata-trail')
expect(metaLead).not.toBeInTheDocument()
expect(metaTrail).toBeInTheDocument()
})
test('does not render metadata when neither provided', () => {
const { container } = render(innhold)
const metadata = container.querySelector('.pkt-card__metadata')
expect(metadata).not.toBeInTheDocument()
})
})
describe('Content placement and structure', () => {
test('renders content elements in correct order', () => {
const tags = [{ text: 'Test Tag' }]
const { container } = render(
innhold
,
)
const article = container.querySelector('article')
const children = Array.from(article?.children || [])
// Image first, then wrapper
expect(children[0]).toHaveClass('pkt-card__image')
expect(children[1]).toHaveClass('pkt-card__wrapper')
const wrapperChildren = Array.from(children[1]?.children || [])
// Order within wrapper: tags (top), header, content, metadata
expect(wrapperChildren[0]).toHaveClass('pkt-card__tags-top')
expect(wrapperChildren[1]).toHaveClass('pkt-card__header')
expect(wrapperChildren[2]).toHaveClass('pkt-card__content')
expect(wrapperChildren[3]).toHaveClass('pkt-card__metadata')
})
test('places tags at bottom when tagPosition is bottom', () => {
const tags = [{ text: 'Test Tag' }]
const { container } = render(
innhold,
)
const wrapperChildren = Array.from(
container.querySelector('.pkt-card__wrapper')?.children || [],
)
// Order: header, content, tags (bottom)
expect(wrapperChildren[0]).toHaveClass('pkt-card__header')
expect(wrapperChildren[1]).toHaveClass('pkt-card__content')
expect(wrapperChildren[2]).toHaveClass('pkt-card__tags-bottom')
})
})
describe('Accessibility', () => {
test('has no accessibility violations', async () => {
const { container } = render(innhold)
const results = await axe(container)
expect(results).toHaveNoViolations()
})
test('applies custom aria-label', () => {
const { container } = render(
innhold,
)
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('Custom accessible label')
})
test('falls back to heading for aria-label', () => {
const { container } = render(innhold)
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('Default Aria Label')
})
test('falls back to "kort" when no heading or aria-label', () => {
const { container } = render(innhold)
const article = container.querySelector('article')
expect(article?.getAttribute('aria-label')).toBe('kort')
})
})
})