@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
269 lines (202 loc) • 9.67 kB
text/typescript
import '@testing-library/jest-dom'
import { fireEvent } from '@testing-library/dom'
import './datepicker'
import '../calendar/calendar'
import { PktDatepicker } from './datepicker'
const waitForCustomElements = async () => {
await customElements.whenDefined('pkt-datepicker')
await customElements.whenDefined('pkt-calendar')
}
// Helper function to create datepicker markup
const createDatepicker = async (datepickerProps = '') => {
const container = document.createElement('div')
container.innerHTML = `
<pkt-datepicker ${datepickerProps}></pkt-datepicker>
`
document.body.appendChild(container)
await waitForCustomElements()
return container
}
// Cleanup after each test
afterEach(() => {
document.body.innerHTML = ''
})
describe('PktDatepicker', () => {
describe('Date input validation and formatting', () => {
test('validates date input format', async () => {
const container = await createDatepicker('label="Test"')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
// Test invalid date input - HTML5 date inputs will reject invalid formats
// For date inputs, we test boundary validation instead
fireEvent.change(input, { target: { value: '2024-02-30' } }) // Invalid date (Feb 30th)
fireEvent.blur(input)
await datepicker.updateComplete
// Should show validation error or handle gracefully
// For HTML5 date inputs, this might not trigger hasError, so we test that it doesn't crash
expect(datepicker).toBeInTheDocument()
})
test('formats dates according to dateformat property', async () => {
const container = await createDatepicker('dateformat="yyyy-MM-dd" value="2024-06-15"')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
expect(input.value).toBe('2024-06-15')
})
test('handles different date formats', async () => {
const testCases = [
{ format: 'dd.MM.yyyy', expected: /\d{2}\.\d{2}\.\d{4}/ },
{ format: 'MM/dd/yyyy', expected: /\d{2}\/\d{2}\/\d{4}/ },
{ format: 'yyyy-MM-dd', expected: /\d{4}-\d{2}-\d{2}/ },
]
for (const testCase of testCases) {
const container = await createDatepicker(
`dateformat="${testCase.format}" value="2024-06-15" multiple`,
)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Multiple mode input should be empty (for new input)
const input = datepicker.querySelector('input') as HTMLInputElement
expect(input.value).toBe('')
// Selected dates should show in tags with custom format
const tag = datepicker.querySelector('pkt-tag time')
expect(tag).toBeInTheDocument()
if (tag) {
expect(tag.textContent).toMatch(testCase.expected)
}
// Cleanup
container.remove()
}
})
test('handles leap year dates correctly', async () => {
const leapYearDate = '2024-02-29'
const container = await createDatepicker(`value="${leapYearDate}"`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
expect(datepicker.value).toBe(leapYearDate)
expect(datepicker.hasError).toBe(false)
})
test('validates February 29 in non-leap years', async () => {
const invalidLeapDate = '2023-02-29'
const container = await createDatepicker(`value="${invalidLeapDate}"`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Should handle gracefully or show error
expect(datepicker).toBeInTheDocument()
})
test('handles edge case dates', async () => {
const edgeDates = ['1900-01-01', '2000-01-01', '2100-12-31', '1999-12-31']
for (const date of edgeDates) {
const container = await createDatepicker(`value="${date}"`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
expect(datepicker.value).toBe(date)
container.remove()
}
})
})
describe('Input field behavior', () => {
test('allows manual date entry', async () => {
const container = await createDatepicker('dateformat="yyyy-MM-dd"')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
fireEvent.change(input, { target: { value: '2024-06-15' } })
fireEvent.blur(input)
await datepicker.updateComplete
expect(datepicker.value).toBe('2024-06-15')
})
test('validates manual date entry', async () => {
const container = await createDatepicker()
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
// HTML5 date inputs reject invalid formats, so we test that the component handles this gracefully
fireEvent.change(input, { target: { value: 'invalid-date' } })
fireEvent.blur(input)
await datepicker.updateComplete
// HTML5 date input will reject invalid dates, component should handle gracefully
expect(datepicker).toBeInTheDocument()
expect(input.value).toBe('') // Invalid dates become empty
})
test('shows placeholder text correctly', async () => {
const placeholderText = 'Select a date'
const container = await createDatepicker(`placeholder="${placeholderText}"`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
expect(input.placeholder).toBe(placeholderText)
})
test('shows help text correctly', async () => {
const helpText = 'Choose your preferred date'
const container = await createDatepicker(`helptext="${helpText}"`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const helpTextElement = datepicker.querySelector('pkt-helptext')
expect(helpTextElement?.textContent).toContain(helpText)
})
test('handles readonly state correctly', async () => {
const container = await createDatepicker('readonly')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const input = datepicker.querySelector('input') as HTMLInputElement
expect(input.readOnly).toBe(true)
// Calendar should still be accessible
const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLButtonElement
expect(calendarButton.disabled).toBe(false)
})
})
describe('Keyboard navigation and interaction', () => {
test('opens calendar with Enter key on calendar button', async () => {
const container = await createDatepicker()
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
if (calendarButton) {
calendarButton.focus()
fireEvent.keyDown(calendarButton, { key: 'Enter' })
await datepicker.updateComplete
expect(datepicker.calendarOpen).toBe(true)
}
})
test('opens calendar with Space key on calendar button', async () => {
const container = await createDatepicker()
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const calendarButton = datepicker.querySelector('button[type="button"]') as HTMLElement
if (calendarButton) {
calendarButton.focus()
fireEvent.keyDown(calendarButton, { key: ' ' })
await datepicker.updateComplete
expect(datepicker.calendarOpen).toBe(true)
}
})
test('closes calendar with Escape key', async () => {
const container = await createDatepicker()
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Open calendar
const calendarButton = datepicker.querySelector('button[type="button"]')
fireEvent.click(calendarButton!)
await datepicker.updateComplete
// Press Escape
fireEvent.keyDown(datepicker, { key: 'Escape' })
await datepicker.updateComplete
expect(datepicker.calendarOpen).toBe(false)
})
test('navigates between tags with arrow keys in multiple mode', async () => {
const multipleDates = '2024-06-15,2024-06-20,2024-06-25'
const container = await createDatepicker(`value="${multipleDates}" multiple`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const tags = datepicker.querySelectorAll('pkt-tag')
const firstTag = tags[0] as HTMLElement
firstTag.focus()
fireEvent.keyDown(firstTag, { key: 'ArrowRight' })
await datepicker.updateComplete
// Focus should move to next tag
expect(document.activeElement).toBeTruthy()
})
})
})