@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
422 lines (340 loc) • 14.7 kB
text/typescript
import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { fireEvent } from '@testing-library/dom'
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import './textinput'
export interface TextinputTestConfig extends BaseTestConfig {
// From PktTextinput specific properties
type?: string
value?: string
autocomplete?: string | null
iconNameRight?: string | null
prefix?: string | null
suffix?: string | null
size?: number | null
omitSearchIcon?: boolean
// From PktInputElement base class (commonly used ones)
id?: string
label?: string
name?: string
disabled?: boolean
readonly?: boolean
required?: boolean
placeholder?: string | null
maxlength?: number | null
minlength?: number | null
hasError?: boolean
errorMessage?: string
helptext?: string
fullwidth?: boolean
counter?: boolean
inline?: boolean
ariaLabelledby?: string | null
ariaDescribedBy?: string | null
}
// Use shared framework
export const createTextinputTest = async (config: TextinputTestConfig = {}) => {
const { container, element } = await createElementTest<
CustomElementFor<'pkt-textinput'>,
TextinputTestConfig
>('pkt-textinput', config)
return {
container,
textinput: element,
}
}
expect.extend(toHaveNoViolations)
afterEach(() => {
document.body.innerHTML = ''
})
describe('PktTextinput', () => {
describe('Basic Rendering', () => {
test('renders without errors', async () => {
const { textinput } = await createTextinputTest()
expect(textinput).toBeInTheDocument()
})
test('renders with default properties', async () => {
const { textinput } = await createTextinputTest()
expect(textinput.type).toBe('text')
expect(textinput.value).toBe('')
expect(textinput.autocomplete).toBe(null) // Property defaults to null, template sets 'off'
})
test('renders input element', async () => {
const { textinput } = await createTextinputTest()
const inputElement = textinput.querySelector('input')
expect(inputElement).toBeInTheDocument()
})
})
describe('Input Types', () => {
test('renders text input by default', async () => {
const { textinput } = await createTextinputTest()
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('text')
})
test('renders email input type', async () => {
const { textinput } = await createTextinputTest({ type: 'email' })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('email')
})
test('renders password input type', async () => {
const { textinput } = await createTextinputTest({ type: 'password' })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('password')
})
test('renders tel input type', async () => {
const { textinput } = await createTextinputTest({ type: 'tel' })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('tel')
})
test('renders url input type', async () => {
const { textinput } = await createTextinputTest({ type: 'url' })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('url')
})
test('renders search input type', async () => {
const { textinput } = await createTextinputTest({ type: 'search' })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe('search')
})
})
describe('Properties and Attributes', () => {
test('sets value correctly', async () => {
const value = 'Test input value'
const { textinput } = await createTextinputTest({ value })
expect(textinput.value).toBe(value)
const inputElement = textinput.querySelector('input') as HTMLInputElement
expect(inputElement.value).toBe(value)
})
test('sets autocomplete correctly', async () => {
const { textinput } = await createTextinputTest({ autocomplete: 'email' })
expect(textinput.autocomplete).toBe('email')
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('autocomplete')).toBe('email')
})
test('handles disabled state', async () => {
const { textinput } = await createTextinputTest({ disabled: true })
const inputElement = textinput.querySelector('input')
expect(inputElement?.hasAttribute('disabled')).toBe(true)
})
test('handles readonly state', async () => {
const { textinput } = await createTextinputTest({ readonly: true })
const inputElement = textinput.querySelector('input')
expect(inputElement?.hasAttribute('readonly')).toBe(true)
})
test('handles required state', async () => {
const { textinput } = await createTextinputTest({ required: true })
const inputElement = textinput.querySelector('input')
expect(inputElement?.hasAttribute('required')).toBe(false) // Not set as attribute on input
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
expect(inputWrapper?.hasAttribute('required')).toBe(true) // But passed to wrapper
})
})
describe('Icons', () => {
test('renders right icon', async () => {
const { textinput } = await createTextinputTest({
iconNameRight: 'search',
})
expect(textinput.iconNameRight).toBe('search')
const icon = textinput.querySelector('pkt-icon')
expect(icon).toBeInTheDocument()
expect(icon?.getAttribute('name')).toBe('search')
})
test('renders search icon for search type by default', async () => {
const { textinput } = await createTextinputTest({ type: 'search' })
const icon = textinput.querySelector('pkt-icon')
expect(icon).toBeInTheDocument()
expect(icon?.getAttribute('name')).toBe('magnifying-glass-big')
})
test('can omit search icon for search type', async () => {
const { textinput } = await createTextinputTest({
type: 'search',
omitSearchIcon: true,
})
const icon = textinput.querySelector('pkt-icon')
expect(icon).not.toBeInTheDocument()
})
})
describe('Prefix and Suffix', () => {
test('renders prefix text', async () => {
const { textinput } = await createTextinputTest({ prefix: 'https://' })
expect(textinput.prefix).toBe('https://')
const prefixElement = textinput.querySelector('.pkt-input-prefix')
expect(prefixElement).toBeInTheDocument()
expect(prefixElement?.textContent).toBe('https://')
})
test('renders suffix text', async () => {
const { textinput } = await createTextinputTest({ suffix: '.com' })
expect(textinput.suffix).toBe('.com')
const suffixElement = textinput.querySelector('.pkt-input-suffix')
expect(suffixElement).toBeInTheDocument()
expect(suffixElement?.textContent?.trim()).toBe('.com')
})
test('renders both prefix and suffix', async () => {
const { textinput } = await createTextinputTest({
prefix: '$',
suffix: 'USD',
})
const prefixElement = textinput.querySelector('.pkt-input-prefix')
const suffixElement = textinput.querySelector('.pkt-input-suffix')
expect(prefixElement?.textContent).toBe('$')
expect(suffixElement?.textContent?.trim()).toBe('USD')
})
})
describe('Input Wrapper Integration', () => {
test('displays label correctly', async () => {
const { textinput } = await createTextinputTest({ label: 'Email Address' })
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
expect(inputWrapper?.getAttribute('label')).toBe('Email Address')
})
test('displays helptext correctly', async () => {
const { textinput } = await createTextinputTest({ helptext: 'Enter a valid email' })
// helptext is passed as a property, not attribute to input-wrapper
expect(textinput.helptext).toBe('Enter a valid email')
})
test('handles error state', async () => {
const { textinput } = await createTextinputTest({
hasError: true,
errorMessage: 'Email is required',
})
expect(textinput.hasError).toBe(true)
expect(textinput.errorMessage).toBe('Email is required')
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
expect(inputWrapper?.hasAttribute('hasError')).toBe(true)
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('aria-invalid')).toBe('true')
})
test('handles fullwidth styling', async () => {
const { textinput } = await createTextinputTest({ fullwidth: true })
const inputElement = textinput.querySelector('input')
expect(inputElement?.className).toContain('pkt-input--fullwidth')
})
})
describe('Character Counter', () => {
test('shows counter when enabled', async () => {
const { textinput } = await createTextinputTest({
counter: true,
maxlength: 50,
})
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
})
test('updates counter on value change', async () => {
const { textinput } = await createTextinputTest({
counter: true,
maxlength: 50,
value: 'Hello',
})
expect(textinput.counterCurrent).toBe(5)
})
})
describe('User Interaction', () => {
test('updates value on user input', async () => {
const { textinput } = await createTextinputTest()
const inputElement = textinput.querySelector('input') as HTMLInputElement
fireEvent.input(inputElement, { target: { value: 'new value' } })
await textinput.updateComplete
expect(textinput.value).toBe('new value')
expect(textinput.touched).toBe(true)
})
test('handles focus and blur events', async () => {
const { textinput } = await createTextinputTest()
const inputElement = textinput.querySelector('input') as HTMLInputElement
// Focus and input to trigger touched state
fireEvent.focus(inputElement)
fireEvent.input(inputElement, { target: { value: 'test input' } })
await textinput.updateComplete
fireEvent.blur(inputElement)
await textinput.updateComplete
// Test that input with value change sets touched state
expect(textinput.touched).toBe(true)
})
})
describe('Validation', () => {
test('respects maxlength constraint', async () => {
const { textinput } = await createTextinputTest({ maxlength: 20 })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('maxlength')).toBe('20')
})
test('respects minlength constraint', async () => {
const { textinput } = await createTextinputTest({ minlength: 3 })
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('minlength')).toBe('3')
})
})
describe('Accessibility', () => {
test('passes through accessibility attributes', async () => {
const { textinput } = await createTextinputTest({
ariaLabelledby: 'external-label',
ariaDescribedBy: 'external-description',
})
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('aria-labelledby')).toBe('external-label')
// ariaDescribedBy is passed as property to input-wrapper
expect(textinput.ariaDescribedBy).toBe('external-description')
})
test('textinput is accessible', async () => {
const { textinput } = await createTextinputTest({
label: 'Email',
type: 'email',
helptext: 'Enter your email address',
required: true,
})
const results = await axe(textinput)
expect(results).toHaveNoViolations()
})
})
describe('Complex Configuration', () => {
test('renders email input with all features', async () => {
const config: TextinputTestConfig = {
type: 'email',
label: 'Email Address',
value: 'user@example.com',
placeholder: 'Enter your email...',
iconNameRight: 'mail',
maxlength: 100,
counter: true,
required: true,
autocomplete: 'email',
helptext: 'We will never share your email',
}
const { textinput } = await createTextinputTest(config)
expect(textinput.type).toBe(config.type)
expect(textinput.value).toBe(config.value)
expect(textinput.iconNameRight).toBe(config.iconNameRight)
expect(textinput.maxlength).toBe(config.maxlength)
expect(textinput.required).toBe(config.required)
expect(textinput.autocomplete).toBe(config.autocomplete)
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
expect(inputWrapper?.getAttribute('label')).toBe(config.label)
expect(textinput.helptext).toBe(config.helptext) // Property, not attribute
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
const inputElement = textinput.querySelector('input')
expect(inputElement?.getAttribute('type')).toBe(config.type)
expect(inputElement?.getAttribute('placeholder')).toBe(config.placeholder)
expect(inputElement?.getAttribute('maxlength')).toBe(String(config.maxlength))
expect(inputElement?.getAttribute('autocomplete')).toBe(config.autocomplete)
const icon = textinput.querySelector('pkt-icon')
expect(icon?.getAttribute('name')).toBe(config.iconNameRight)
})
test('renders URL input with prefix and suffix', async () => {
const config: TextinputTestConfig = {
type: 'url',
label: 'Website URL',
prefix: 'https://',
suffix: '.com',
placeholder: 'example',
fullwidth: true,
}
const { textinput } = await createTextinputTest(config)
expect(textinput.prefix).toBe(config.prefix)
expect(textinput.suffix).toBe(config.suffix)
const prefixElement = textinput.querySelector('.pkt-input-prefix')
const suffixElement = textinput.querySelector('.pkt-input-suffix')
expect(prefixElement?.textContent).toBe(config.prefix)
expect(suffixElement?.textContent?.trim()).toBe(config.suffix)
const inputElement = textinput.querySelector('input')
expect(inputElement?.className).toContain('pkt-input--fullwidth')
})
})
})