import '@testing-library/jest-dom' import { fireEvent, render, screen } from '@testing-library/react' import { axe, toHaveNoViolations } from 'jest-axe' import { vi } from 'vitest' import { IPktDatepicker, PktDatepicker } from './Datepicker' expect.extend(toHaveNoViolations) const datePickerId = 'datepickerId' const label = 'Date Picker Label' const createDatepickerTest = (props: Partial = {}) => { const defaultProps: IPktDatepicker = { label, id: datePickerId, ...props, } return render() } describe('PktDatepicker', () => { describe('Event handling', () => { test('dispatches onChange event when value changes', () => { const handleChange = vi.fn() const { container } = createDatepickerTest({ onChange: handleChange, }) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '2024-06-15' } }) expect(handleChange).toHaveBeenCalled() }) test('dispatches onValueChange event when value changes', () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ onValueChange: handleValueChange, }) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '2024-06-15' } }) expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15']) }) test('input can receive focus', () => { const { container } = createDatepickerTest() const input = container.querySelector('input[type="date"]') as HTMLInputElement input.focus() expect(document.activeElement).toBe(input) }) test('input can be blurred', () => { const { container } = createDatepickerTest() const input = container.querySelector('input[type="date"]') as HTMLInputElement input.focus() expect(document.activeElement).toBe(input) input.blur() expect(document.activeElement).not.toBe(input) }) test('dispatches both onChange and onValueChange', () => { const handleChange = vi.fn() const handleValueChange = vi.fn() const { container } = createDatepickerTest({ onChange: handleChange, onValueChange: handleValueChange, }) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '2024-06-15' } }) expect(handleChange).toHaveBeenCalled() expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15']) }) }) describe('Accessibility', () => { test('has no accessibility violations with single date set', async () => { const { container } = createDatepickerTest({ value: '2024-06-15', helptext: 'Choose a date from the calendar', }) const results = await axe(container) expect(results).toHaveNoViolations() }) test('has no accessibility violations with range dates set', async () => { const { container } = createDatepickerTest({ range: true, value: ['2024-06-15', '2024-06-25'], helptext: 'Choose start and end dates', }) const results = await axe(container) expect(results).toHaveNoViolations() }) test('has no accessibility violations with multiple dates', async () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15', '2024-06-20', '2024-06-25'], helptext: 'Choose multiple dates', }) const results = await axe(container) expect(results).toHaveNoViolations() }) test('has no accessibility violations when disabled', async () => { const { container } = createDatepickerTest({ disabled: true, helptext: 'This field is disabled', }) const results = await axe(container) expect(results).toHaveNoViolations() }) test('has no accessibility violations with error state', async () => { const { container } = createDatepickerTest({ hasError: true, errorMessage: 'Please enter a valid date', helptext: 'Required field', }) const results = await axe(container) expect(results).toHaveNoViolations() }) test('associates label with input correctly', () => { const { container } = createDatepickerTest() const labelEl = container.querySelector('label') expect(labelEl).toBeInTheDocument() expect(labelEl).toHaveAttribute('for', `${datePickerId}-input`) const input = container.querySelector(`#${datePickerId}-input`) expect(input).toBeInTheDocument() }) test('has proper ARIA attributes for calendar button', () => { const { container } = createDatepickerTest() const button = container.querySelector('button[type="button"]') expect(button).toBeInTheDocument() expect(button).toHaveAttribute('aria-label', 'Åpne kalender') }) test('input has aria-describedby when helptext is present', () => { const { container } = createDatepickerTest({ helptext: 'Some help text', }) const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).toHaveAttribute('aria-describedby', `${datePickerId}-helptext`) }) test('input does not have aria-describedby when no helptext', () => { const { container } = createDatepickerTest() const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).not.toHaveAttribute('aria-describedby') }) test('input has aria-invalid when hasError is true', () => { const { container } = createDatepickerTest({ hasError: true, errorMessage: 'Invalid date', }) const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).toHaveAttribute('aria-invalid', 'true') }) test('input has aria-errormessage when hasError is true', () => { const { container } = createDatepickerTest({ hasError: true, errorMessage: 'Invalid date', }) const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).toHaveAttribute('aria-errormessage', `${datePickerId}-error`) }) test('input does not have aria-invalid when no error', () => { const { container } = createDatepickerTest() const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).not.toHaveAttribute('aria-invalid', 'true') }) test('calendar popup has aria-hidden when closed', () => { const { container } = createDatepickerTest() const popup = container.querySelector('.pkt-calendar-popup') expect(popup).toHaveAttribute('aria-hidden', 'true') }) test('calendar popup has aria-hidden false when open', () => { const { container } = createDatepickerTest() const button = container.querySelector('button[type="button"]') as HTMLButtonElement fireEvent.click(button) const popup = container.querySelector('.pkt-calendar-popup') expect(popup).toHaveAttribute('aria-hidden', 'false') }) test('date tags container has aria-live polite', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15'], }) const tagsContainer = container.querySelector('.pkt-date-tags') expect(tagsContainer).toHaveAttribute('aria-live', 'polite') }) test('handles focus management - button can be focused', () => { const { container } = createDatepickerTest() const button = container.querySelector('button[type="button"]') as HTMLButtonElement button.focus() expect(document.activeElement).toBe(button) }) test('supports keyboard-only interaction to open and close calendar', () => { const { container } = createDatepickerTest() const input = container.querySelector('input[type="date"]') as HTMLInputElement // Open with Space fireEvent.keyDown(input, { key: ' ' }) let popup = container.querySelector('.pkt-calendar-popup') expect(popup).not.toHaveAttribute('hidden') // Close with Escape fireEvent.keyDown(document, { key: 'Escape' }) popup = container.querySelector('.pkt-calendar-popup') expect(popup).toHaveAttribute('hidden') }) }) describe('Localization', () => { test('uses custom strings for calendar button', () => { const { container } = createDatepickerTest({ strings: { calendar: { buttonAltText: 'Open calendar' }, }, }) const button = container.querySelector('button[type="button"]') expect(button).toHaveAttribute('aria-label', 'Open calendar') }) test('uses custom strings for range labels', () => { const { container } = createDatepickerTest({ range: true, showRangeLabels: true, strings: { generic: { from: 'From', to: 'To' }, calendar: { buttonAltText: 'Open calendar' }, }, }) const prefix = container.querySelector('.pkt-input-prefix') expect(prefix).toHaveTextContent('From') }) test('uses default Norwegian strings', () => { const { container } = createDatepickerTest({ range: true, showRangeLabels: true, }) const button = container.querySelector('button[type="button"]') expect(button).toHaveAttribute('aria-label', 'Åpne kalender') const prefix = container.querySelector('.pkt-input-prefix') expect(prefix).toHaveTextContent('Fra') }) }) describe('InputWrapper integration', () => { test('renders helptext', () => { createDatepickerTest({ helptext: 'Please select a date', }) expect(screen.getByText('Please select a date')).toBeInTheDocument() }) test('renders error message when hasError is true', () => { createDatepickerTest({ hasError: true, errorMessage: 'Date is required', }) expect(screen.getByText('Date is required')).toBeInTheDocument() }) test('renders optional tag', () => { const { container } = createDatepickerTest({ optionalTag: true, optionalText: 'Valgfritt', }) expect(container.textContent).toContain('Valgfritt') }) test('renders required tag', () => { const { container } = createDatepickerTest({ requiredTag: true, requiredText: 'Må fylles ut', }) expect(container.textContent).toContain('Må fylles ut') }) test('applies fullwidth class to input', () => { const { container } = createDatepickerTest({ fullwidth: true, }) const input = container.querySelector('.pkt-datepicker__input') as HTMLInputElement expect(input).toHaveClass('pkt-input--fullwidth') }) }) describe('CSS classes', () => { test('applies pkt-datepicker class to outer wrapper', () => { const { container } = createDatepickerTest() const wrapper = container.querySelector('.pkt-datepicker') expect(wrapper).toBeInTheDocument() }) test('applies pkt-datepicker__inputs class', () => { const { container } = createDatepickerTest() const inputs = container.querySelector('.pkt-datepicker__inputs') expect(inputs).toBeInTheDocument() }) test('applies datepicker input classes', () => { const { container } = createDatepickerTest() const input = container.querySelector('.pkt-datepicker__input') expect(input).toBeInTheDocument() expect(input).toHaveClass('pkt-input') }) test('applies range class for range mode', () => { const { container } = createDatepickerTest({ range: true, }) const inputs = container.querySelectorAll('.pkt-datepicker--range') expect(inputs.length).toBe(2) }) test('applies multiple class for multiple mode', () => { const { container } = createDatepickerTest({ multiple: true, }) const input = container.querySelector('.pkt-datepicker--multiple') expect(input).toBeInTheDocument() }) test('applies calendar button classes', () => { const { container } = createDatepickerTest() const button = container.querySelector('button[type="button"]') expect(button).toHaveClass('pkt-input-icon') expect(button).toHaveClass('pkt-btn') expect(button).toHaveClass('pkt-btn--icon-only') expect(button).toHaveClass('pkt-btn--tertiary') expect(button).toHaveClass('pkt-datepicker__calendar-button') }) test('popup has show/hide classes based on open state', () => { const { container } = createDatepickerTest() const popup = container.querySelector('.pkt-calendar-popup') expect(popup).toHaveClass('hide') const button = container.querySelector('button[type="button"]') as HTMLButtonElement fireEvent.click(button) expect(popup).toHaveClass('show') }) }) })