import '@testing-library/jest-dom' import { type ChangeEvent, useState } from 'react' import { act, fireEvent, render, waitFor } from '@testing-library/react' import { vi } from 'vitest' import { IPktDatepicker, PktDatepicker } from './Datepicker' const datePickerId = 'datepickerId' const label = 'Date Picker Label' const createDatepickerTest = (props: Partial = {}) => { const defaultProps: IPktDatepicker = { label, id: datePickerId, ...props, } return render() } describe('PktDatepicker', () => { describe('Multiple date selection', () => { test('displays multiple selected dates as tags', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15', '2024-06-20', '2024-06-25'], }) const tags = container.querySelectorAll('.pkt-date-tags .pkt-tag') expect(tags.length).toBe(3) }) test('removes dates when clicking tag close button', async () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15', '2024-06-20'], onValueChange: handleValueChange, }) const closeButtons = container.querySelectorAll('.pkt-date-tags button.pkt-tag') expect(closeButtons.length).toBe(2) fireEvent.click(closeButtons[0]) await waitFor(() => { expect(handleValueChange).toHaveBeenCalledWith(['2024-06-20']) }) }) test('respects maxlength in multiple mode', () => { const { container } = createDatepickerTest({ multiple: true, maxlength: 2, value: ['2024-06-15', '2024-06-20'], }) const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).toBeDisabled() }) test('renders counter when maxlength is set', () => { const { container } = createDatepickerTest({ multiple: true, maxlength: 5, value: ['2024-06-15', '2024-06-20'], }) const counter = container.querySelector('.pkt-input__counter') expect(counter).toBeInTheDocument() }) test('does not disable input when maxlength is not reached', () => { const { container } = createDatepickerTest({ multiple: true, maxlength: 5, value: ['2024-06-15', '2024-06-20'], }) const input = container.querySelector('input[type="date"]') as HTMLInputElement expect(input).not.toBeDisabled() }) test('sorts multiple dates chronologically', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-25', '2024-06-15', '2024-06-20'], }) const times = container.querySelectorAll('.pkt-date-tags time') expect(times[0]).toHaveAttribute('datetime', '2024-06-15') expect(times[1]).toHaveAttribute('datetime', '2024-06-20') expect(times[2]).toHaveAttribute('datetime', '2024-06-25') }) test('sorts multiple dates chronologically across months and years', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2025-01-10', '2024-12-25', '2024-06-15'], }) const times = container.querySelectorAll('.pkt-date-tags time') expect(times[0]).toHaveAttribute('datetime', '2024-06-15') expect(times[1]).toHaveAttribute('datetime', '2024-12-25') expect(times[2]).toHaveAttribute('datetime', '2025-01-10') }) test('dispatches onValueChange with remaining dates when tag removed', async () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15', '2024-06-20', '2024-06-25'], onValueChange: handleValueChange, }) const closeButtons = container.querySelectorAll('.pkt-date-tags button.pkt-tag') fireEvent.click(closeButtons[1]) // remove middle date (2024-06-20) await waitFor(() => { expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15', '2024-06-25']) }) }) test('renders date tags with formatted dates', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15'], dateformat: 'yyyy-MM-dd', }) const time = container.querySelector('.pkt-date-tags time') expect(time).toBeInTheDocument() expect(time).toHaveTextContent('2024-06-15') }) test('renders date tags with default dd.MM.yyyy format', () => { const { container } = createDatepickerTest({ multiple: true, value: ['2024-06-15'], }) const time = container.querySelector('.pkt-date-tags time') expect(time).toBeInTheDocument() expect(time).toHaveTextContent('15.06.2024') }) }) describe('Range selection', () => { test('renders two inputs for range mode', () => { const { container } = createDatepickerTest({ range: true, }) const inputs = container.querySelectorAll('input[type="date"]') expect(inputs.length).toBe(2) }) test('populates both input fields when initialized with range value', () => { const { container } = createDatepickerTest({ range: true, value: ['2024-06-15', '2024-06-25'], }) const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf expect(inputs[0].value).toBe('2024-06-15') expect(inputs[1].value).toBe('2024-06-25') }) test('displays range labels when showRangeLabels is true', () => { const { container } = createDatepickerTest({ range: true, showRangeLabels: true, }) const prefix = container.querySelector('.pkt-input-prefix') expect(prefix).toBeInTheDocument() expect(prefix).toHaveTextContent('Fra') }) test('renders separator between range inputs', () => { const { container } = createDatepickerTest({ range: true, }) const separator = container.querySelector('.pkt-input-separator') expect(separator).toBeInTheDocument() expect(separator).toHaveTextContent('–') }) test('hides separator when showRangeLabels is true', () => { const { container } = createDatepickerTest({ range: true, showRangeLabels: true, }) const separator = container.querySelector('.pkt-input-separator') expect(separator).not.toBeInTheDocument() }) test('dispatches onValueChange for partial range (only from)', () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ range: true, onValueChange: handleValueChange, }) const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf fireEvent.change(inputs[0], { target: { value: '2024-06-15' } }) expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15']) }) test('dispatches onValueChange when both range dates are present', () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ range: true, value: ['2024-06-15'], onValueChange: handleValueChange, }) const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf fireEvent.change(inputs[1], { target: { value: '2024-06-25' } }) expect(handleValueChange).toHaveBeenCalledWith(['2024-06-15', '2024-06-25']) }) test('validates range order on blur (rejects end before start)', () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ range: true, value: ['2024-06-25', '2024-06-15'], onValueChange: handleValueChange, }) const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf fireEvent.blur(inputs[1]) // Should reset to just the from-date since range order is invalid expect(handleValueChange).toHaveBeenCalledWith(['2024-06-25']) }) test('applies range input classes to container', () => { const { container } = createDatepickerTest({ range: true, showRangeLabels: true, }) const inputsDiv = container.querySelector('.pkt-datepicker__inputs') expect(inputsDiv).toHaveClass('pkt-input__range-inputs') }) test('range inputs have aria-labels with label and from/to strings', () => { const { container } = createDatepickerTest({ range: true, }) const fromInput = container.querySelector(`#${datePickerId}-input`) as HTMLInputElement expect(fromInput).toHaveAttribute('aria-label', `${label} Fra`) const toInput = container.querySelector(`#${datePickerId}-to`) as HTMLInputElement expect(toInput).toHaveAttribute('aria-label', `${label} Til`) }) test('controlled range with onChange updates value via useState', async () => { function ControlledRange() { const [val, setVal] = useState('2024-06-15,2024-06-25') return ( <> ) => setVal(e.target.value)} /> {val} ) } const { container, getByTestId } = render() const inputs = container.querySelectorAll('input[type="date"]') as NodeListOf // Change the "from" date fireEvent.change(inputs[0], { target: { value: '2024-06-10' } }) await waitFor(() => { expect(getByTestId('output').textContent).toBe('2024-06-10,2024-06-25') }) // Change the "to" date fireEvent.change(inputs[1], { target: { value: '2024-06-30' } }) await waitFor(() => { expect(getByTestId('output').textContent).toBe('2024-06-10,2024-06-30') }) }) }) describe('Single date selection', () => { test('dispatches onChange when input 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 with array when input 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('dispatches onValueChange with empty array when cleared', () => { const handleValueChange = vi.fn() const { container } = createDatepickerTest({ value: '2024-06-15', onValueChange: handleValueChange, }) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '' } }) expect(handleValueChange).toHaveBeenCalledWith([]) }) }) describe('Form reset', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) test('resets single value to defaultValue on form reset', () => { const { container } = render(
, ) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '2024-12-25' } }) const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement expect(formInput.value).toBe('2024-12-25') const form = container.querySelector('form')! act(() => { fireEvent.reset(form) vi.advanceTimersByTime(10) }) expect(formInput.value).toBe('2024-06-15') }) test('resets to empty when no defaultValue on form reset', () => { const { container } = render(
, ) const input = container.querySelector('input[type="date"]') as HTMLInputElement fireEvent.change(input, { target: { value: '2024-06-15' } }) const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement expect(formInput.value).toBe('2024-06-15') const form = container.querySelector('form')! act(() => { fireEvent.reset(form) vi.advanceTimersByTime(10) }) expect(formInput.value).toBe('') }) }) })