UNPKG

@oslokommune/punkt-elements

Version:

Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo

422 lines (340 loc) 14.7 kB
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') }) }) })