import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { axe, toHaveNoViolations } from 'jest-axe'
import { vi } from 'vitest'
import { createRef } from 'react'
import { useForm } from 'react-hook-form'
import { PktButton } from './Button'
expect.extend(toHaveNoViolations)
// Mock Form Component
const MockForm = ({ buttonPosition }: { buttonPosition: 'inside' | 'outside' }) => {
const { register, handleSubmit, reset } = useForm()
const onSubmit = vi.fn()
return (
<>
{buttonPosition === 'outside' && (
Submit
)}
{buttonPosition === 'outside' && (
reset()}>
Reset
)}
>
)
}
// Ligger button utenfor form i React Hook Form må bruker sende inn reset selv
// dette gjelder ikke native knapper som er assosiert med native form elementer, men de fleste brukerene våre
// bruker nok også våre input elementer også
//https://react-hook-form.com/api/useform/reset
describe('PktButton Form Behavior', () => {
test('PktButton submits form when inside form element', async () => {
render()
await userEvent.type(screen.getByPlaceholderText('Name'), 'Shawn Joe')
await userEvent.click(screen.getByText('Submit'))
expect(screen.getByPlaceholderText('Name')).toHaveValue('Shawn Joe')
})
test('PktButton resets form when inside form element', async () => {
render()
await userEvent.type(screen.getByPlaceholderText('Name'), 'Shawn Joe')
await userEvent.click(screen.getByText('Reset'))
expect(screen.getByPlaceholderText('Name')).toHaveValue('')
})
test('PktButton submits form when outside but linked via form attribute', async () => {
render()
await userEvent.type(screen.getByPlaceholderText('Name'), 'Shawn Joe')
await userEvent.click(screen.getByText('Submit'))
expect(screen.getByPlaceholderText('Name')).toHaveValue('Shawn Joe')
})
test('PktButton resets form when outside but linked via form attribute', async () => {
render()
await userEvent.type(screen.getByPlaceholderText('Name'), 'Shawn Joe')
await userEvent.click(screen.getByText('Reset'))
// Wait for the form state update
await screen.findByPlaceholderText('Name')
expect(screen.getByPlaceholderText('Name')).toHaveValue('')
})
})
test('forwardRef works correctly', async () => {
const ref = createRef()
render(Click me)
await userEvent.click(screen.getByText('Click me'))
expect(ref.current).toBe(screen.getByRole('button'))
})
test('PktButton triggers click when focused and enter is pressed', async () => {
const handleClick = vi.fn()
render(trøkk her)
const button = screen.getByRole('button', { name: /trøkk her/i })
button.focus()
expect(button).toHaveFocus()
await userEvent.keyboard('{Enter}')
expect(handleClick).toHaveBeenCalledTimes(1)
})
test('PktButton triggers click when focused and space is pressed', async () => {
const handleClick = vi.fn()
render(Trøkk igjen)
const button = screen.getByRole('button')
button.focus()
expect(button).toHaveFocus()
await userEvent.keyboard(' ')
expect(handleClick).toHaveBeenCalledTimes(1)
})
describe('accessibility', () => {
it('renders with no wcag errors with axe', async () => {
const { container } = render()
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})
describe('PktButton icon paths', () => {
test('passes iconPath to PktIcon when provided', () => {
const customPath = 'https://custom-cdn.example.com/icons/'
render(
Test Button
,
)
const icon = document.querySelector('.pkt-btn__icon:not(.pkt-btn__spinner)') as HTMLElement & { path?: string }
expect(icon?.path).toBe(customPath)
})
test('does not set path property when iconPath is not provided', () => {
render(
Test Button
,
)
const icon = document.querySelector('.pkt-btn__icon:not(.pkt-btn__spinner)') as HTMLElement & { path?: string }
// When not provided, path uses PktIcon's default
expect(icon?.path).toBe('https://punkt-cdn.oslo.kommune.no/latest/icons/')
})
test('passes secondIconPath to second PktIcon when provided', () => {
const customPath = 'https://custom-cdn.example.com/icons/'
render(
Test Button
,
)
const icons = document.querySelectorAll('.pkt-btn__icon:not(.pkt-btn__spinner)') as NodeListOf<
HTMLElement & { path?: string }
>
expect(icons).toHaveLength(2)
expect(icons[1]?.path).toBe(customPath)
})
test('handles both iconPath and secondIconPath independently', () => {
const iconPath = 'https://custom-cdn.example.com/icons/'
const secondIconPath = 'https://another-cdn.example.com/icons/'
render(
Test Button
,
)
const icons = document.querySelectorAll('.pkt-btn__icon:not(.pkt-btn__spinner)') as NodeListOf<
HTMLElement & { path?: string }
>
expect(icons).toHaveLength(2)
expect(icons[0]?.path).toBe(iconPath)
expect(icons[1]?.path).toBe(secondIconPath)
})
})