@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
350 lines (270 loc) • 12.9 kB
text/typescript
import '@testing-library/jest-dom'
import { fireEvent } from '@testing-library/dom'
import './datepicker'
import '../calendar/calendar'
import { PktDatepicker } from './datepicker'
import { PktDatepickerPopup } from './datepicker-popup'
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('Multiple date selection', () => {
test('displays multiple selected dates as tags', 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')
expect(tags.length).toBe(3)
})
test('allows adding dates through calendar in multiple mode', async () => {
const container = await createDatepicker('multiple calendar-open')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const popup = datepicker.querySelector('pkt-datepicker-popup') as PktDatepickerPopup
await popup?.updateComplete
const calendar = datepicker.querySelector('pkt-calendar')
await (calendar as any)?.updateComplete
// Select a date
const availableDate = datepicker.querySelector('[data-date]:not([data-disabled="disabled"])')
if (availableDate) {
fireEvent.click(availableDate)
await datepicker.updateComplete
// Should add tag
const tags = datepicker.querySelectorAll('pkt-tag')
expect(tags.length).toBe(1)
}
})
test('removes dates when clicking tag close button', async () => {
const multipleDates = '2024-06-15,2024-06-20'
const container = await createDatepicker(`value="${multipleDates}" multiple`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const closeButtons = datepicker.querySelectorAll('pkt-tag .pkt-tag__close-btn')
expect(closeButtons.length).toBe(2)
// Click first close button
fireEvent.click(closeButtons[0])
await datepicker.updateComplete
const remainingTags = datepicker.querySelectorAll('pkt-tag')
expect(remainingTags.length).toBe(1)
})
test('respects maxlength in multiple mode', async () => {
const container = await createDatepicker('multiple maxlength="2"')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Open calendar and try to select more than maxlength dates
const calendarButton = datepicker.querySelector('button[type="button"]')
fireEvent.click(calendarButton!)
await datepicker.updateComplete
const availableDates = datepicker.querySelectorAll(
'.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
)
// Select 3 dates
fireEvent.click(availableDates[0])
await datepicker.updateComplete
fireEvent.click(availableDates[1])
await datepicker.updateComplete
fireEvent.click(availableDates[2])
await datepicker.updateComplete
// Should only have maxlength tags
const tags = datepicker.querySelectorAll('pkt-tag')
expect(tags.length).toBeLessThanOrEqual(2)
})
test('sorts multiple dates chronologically', async () => {
const unsortedDates = '2024-06-25,2024-06-15,2024-06-20'
const container = await createDatepicker(`value="${unsortedDates}" multiple`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const tags = datepicker.querySelectorAll('pkt-tag')
const tagTexts = Array.from(tags).map((tag) => tag.textContent?.trim())
// Should be sorted chronologically
expect(tagTexts[0]).toContain('15')
expect(tagTexts[1]).toContain('20')
expect(tagTexts[2]).toContain('25')
})
test('sorts multiple dates chronologically across months and years', async () => {
// Test dates that would fail with simple string sorting
const complexUnsortedDates = '2024-12-01,2023-01-15,2024-01-01,2023-12-31'
const container = await createDatepicker(`value="${complexUnsortedDates}" multiple`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const tags = datepicker.querySelectorAll('pkt-tag')
const tagTexts = Array.from(tags).map((tag) =>
tag.querySelector('time')?.getAttribute('datetime'),
)
// Should be sorted chronologically: 2023-01-15, 2023-12-31, 2024-01-01, 2024-12-01
expect(tagTexts[0]).toBe('2023-01-15')
expect(tagTexts[1]).toBe('2023-12-31')
expect(tagTexts[2]).toBe('2024-01-01')
expect(tagTexts[3]).toBe('2024-12-01')
})
})
describe('Range selection', () => {
test('displays range labels when showRangeLabels is true', async () => {
const rangeValue = '2024-06-15,2024-06-20'
const container = await createDatepicker(`value="${rangeValue}" range show-range-labels`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
expect(datepicker.showRangeLabels).toBe(true)
const rangeLabels = datepicker.querySelectorAll('.pkt-input-prefix')
expect(rangeLabels.length).toBeGreaterThan(0)
})
test('populates both input fields when initialized with range value', async () => {
const rangeValue = '2024-06-15,2024-06-20'
const container = await createDatepicker(`value="${rangeValue}" range`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
const inputs = datepicker.querySelectorAll('input')
expect(inputs.length).toBe(2)
// Check that both input fields are populated
expect(inputs[0].value).toBe('2024-06-15')
expect(inputs[1].value).toBe('2024-06-20')
// Check internal state
expect(datepicker._value).toEqual(['2024-06-15', '2024-06-20'])
expect(datepicker.value).toBe('2024-06-15,2024-06-20')
})
test('dispatches value-change event with array for range and multiple datepickers', async () => {
// Test range datepicker
const rangeContainer = await createDatepicker('range')
const rangeDatepicker = rangeContainer.querySelector('pkt-datepicker') as PktDatepicker
await rangeDatepicker.updateComplete
let valueChangeEvent: CustomEvent | null = null
rangeDatepicker.addEventListener('value-change', (e: Event) => {
valueChangeEvent = e as CustomEvent
})
// Set a range value programmatically
rangeDatepicker.value = '2024-06-15,2024-06-20'
await rangeDatepicker.updateComplete
expect(valueChangeEvent).toBeTruthy()
expect(valueChangeEvent!.detail).toEqual(['2024-06-15', '2024-06-20'])
// Test multiple datepicker
const multipleContainer = await createDatepicker('multiple')
const multipleDatepicker = multipleContainer.querySelector('pkt-datepicker') as PktDatepicker
await multipleDatepicker.updateComplete
valueChangeEvent = null
multipleDatepicker.addEventListener('value-change', (e: Event) => {
valueChangeEvent = e as CustomEvent
})
// Set multiple values programmatically
multipleDatepicker.value = '2024-06-15,2024-06-20,2024-06-25'
await multipleDatepicker.updateComplete
expect(valueChangeEvent).toBeTruthy()
expect(valueChangeEvent!.detail).toEqual(['2024-06-15', '2024-06-20', '2024-06-25'])
// Test single datepicker
const singleContainer = await createDatepicker('')
const singleDatepicker = singleContainer.querySelector('pkt-datepicker') as PktDatepicker
await singleDatepicker.updateComplete
valueChangeEvent = null
singleDatepicker.addEventListener('value-change', (e: Event) => {
valueChangeEvent = e as CustomEvent
})
// Set single value programmatically
singleDatepicker.value = '2024-06-15'
await singleDatepicker.updateComplete
expect(valueChangeEvent).toBeTruthy()
expect(valueChangeEvent!.detail).toBe('2024-06-15')
})
test('handles range selection through calendar', async () => {
const container = await createDatepicker('range')
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
const availableDates = datepicker.querySelectorAll(
'[data-date]:not([data-disabled="disabled"])',
)
// Select start date
if (availableDates.length > 5) {
fireEvent.click(availableDates[5])
await datepicker.updateComplete
// Select end date
if (availableDates.length > 10) {
fireEvent.click(availableDates[10])
await datepicker.updateComplete
// Should have range value
expect(datepicker.value).toContain(',')
const values = (datepicker.value as string).split(',')
expect(values.length).toBe(2)
}
}
})
test('validates range order', async () => {
const invalidRange = '2024-06-20,2024-06-15' // End before start
const container = await createDatepicker(`value="${invalidRange}" range`)
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Should reject invalid range and clear the value
expect(datepicker.value).toBe('')
expect(datepicker._value).toEqual([])
// Component should still render without errors
expect(datepicker).toBeInTheDocument()
// Test with valid range should work
datepicker.value = '2024-06-15,2024-06-20'
await datepicker.updateComplete
expect(datepicker.value).toBe('2024-06-15,2024-06-20')
expect(datepicker._value).toEqual(['2024-06-15', '2024-06-20'])
})
test('handles range validation edge cases', async () => {
const container = await createDatepicker('range')
const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
await datepicker.updateComplete
// Same start and end date should be valid
datepicker.value = '2024-06-15,2024-06-15'
await datepicker.updateComplete
expect(datepicker.value).toBe('2024-06-15,2024-06-15')
// Single date should be valid (incomplete range)
datepicker.value = '2024-06-15'
await datepicker.updateComplete
expect(datepicker.value).toBe('2024-06-15')
// Empty value should be valid
datepicker.value = ''
await datepicker.updateComplete
expect(datepicker.value).toBe('')
// Multiple invalid attempts should be consistently rejected
datepicker.value = '2024-06-25,2024-06-10'
await datepicker.updateComplete
expect(datepicker.value).toBe('')
datepicker.value = '2024-12-31,2024-01-01'
await datepicker.updateComplete
expect(datepicker.value).toBe('')
})
test('shows range preview on hover', async () => {
const container = await createDatepicker('range')
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
const availableDates = datepicker.querySelectorAll(
'.pkt-calendar__date:not(.pkt-calendar__date--disabled)',
)
// Select start date
fireEvent.click(availableDates[5])
await datepicker.updateComplete
// Hover over potential end date
fireEvent.mouseOver(availableDates[10])
await datepicker.updateComplete
// Should show hover preview
const hoverRanges = datepicker.querySelectorAll('.pkt-calendar__date--in-range-hover')
expect(hoverRanges.length).toBeGreaterThan(0)
})
})
})