/** * Component tests for SvDateTimePicker: the masked input round-trips through the * token engine, the dropdown portals to with DATE/TIME tabs, and picking * a day preserves the time (and vice versa). */ import { describe, expect, it, afterEach } from 'vitest' import { mount, unmount, flushSync } from 'svelte' import SvDateTimePicker from './SvDateTimePicker.svelte' function mountDtp(props: Record) { const target = document.createElement('div') document.body.appendChild(target) const app = mount(SvDateTimePicker, { target, props: props as any }) flushSync() return { target, destroy: () => { unmount(app); target.remove() } } } // The panel portals to , so query the document, not the mount target. const panel = () => document.querySelector('.sv-dtp__panel') afterEach(() => { document.querySelectorAll('.sv-dtp__panel').forEach((n) => n.remove()) }) describe('SvDateTimePicker input', () => { it('formats the initial value with the mask', () => { const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7, 9, 4), formatString: 'yyyy-MM-dd HH:mm' }) try { const input = target.querySelector('.sv-dtp__input')! expect(input.value).toBe('2026-06-07 09:04') } finally { destroy() } }) it('parses typed text on blur and emits a Date', () => { let got: Date | null | undefined const { target, destroy } = mountDtp({ value: null, formatString: 'yyyy-MM-dd HH:mm', onChange: (d: Date | null) => (got = d), }) try { const input = target.querySelector('.sv-dtp__input')! input.value = '2026-06-07 14:30' input.dispatchEvent(new Event('input', { bubbles: true })) input.dispatchEvent(new FocusEvent('blur')) flushSync() expect(got).toBeInstanceOf(Date) expect(got!.getFullYear()).toBe(2026) expect(got!.getHours()).toBe(14) expect(got!.getMinutes()).toBe(30) } finally { destroy() } }) it('reverts invalid text back to the last good value', () => { const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7, 9, 0), formatString: 'yyyy-MM-dd HH:mm' }) try { const input = target.querySelector('.sv-dtp__input')! input.value = 'garbage' input.dispatchEvent(new Event('input', { bubbles: true })) input.dispatchEvent(new FocusEvent('blur')) flushSync() expect(input.value).toBe('2026-06-07 09:00') } finally { destroy() } }) it('clamps to max', () => { let got: Date | null | undefined const { target, destroy } = mountDtp({ value: null, formatString: 'yyyy-MM-dd HH:mm', max: new Date(2026, 5, 10, 0, 0), onChange: (d: Date | null) => (got = d), }) try { const input = target.querySelector('.sv-dtp__input')! input.value = '2026-12-31 23:59' input.dispatchEvent(new Event('input', { bubbles: true })) input.dispatchEvent(new FocusEvent('blur')) flushSync() expect(got!.getTime()).toBe(new Date(2026, 5, 10, 0, 0).getTime()) } finally { destroy() } }) it('clear button nulls a nullable value', () => { let got: Date | null | undefined = new Date() const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7), nullable: true, onChange: (d: Date | null) => (got = d), }) try { target.querySelector('.sv-dtp__clear')!.click() flushSync() expect(got).toBeNull() } finally { destroy() } }) }) describe('SvDateTimePicker dropdown', () => { it('opens a portalled panel with DATE and TIME tabs', () => { const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7, 9, 0) }) try { target.querySelector('.sv-dtp__toggle')!.click() flushSync() expect(panel()).not.toBeNull() const tabs = panel()!.querySelectorAll('[role="tab"]') expect(tabs.length).toBe(2) expect(tabs[0]!.textContent).toBe('DATE') expect(tabs[1]!.textContent).toBe('TIME') } finally { destroy() } }) it('picking a calendar day preserves the time', () => { let got: Date | null | undefined const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7, 9, 45), onChange: (d: Date | null) => (got = d), }) try { target.querySelector('.sv-dtp__toggle')!.click() flushSync() const day20 = Array.from(panel()!.querySelectorAll('.sv-cal__day')).find( (b) => b.textContent?.trim() === '20' && !b.classList.contains('is-outside'), )! day20.click() flushSync() expect(got!.getDate()).toBe(20) expect(got!.getHours()).toBe(9) // time preserved expect(got!.getMinutes()).toBe(45) } finally { destroy() } }) it('honors dropDownDisplayMode=calendar (no tabs, calendar only)', () => { const { target, destroy } = mountDtp({ value: new Date(2026, 5, 7), dropDownDisplayMode: 'calendar' }) try { target.querySelector('.sv-dtp__toggle')!.click() flushSync() expect(panel()!.querySelectorAll('[role="tab"]').length).toBe(0) expect(panel()!.querySelector('.sv-cal')).not.toBeNull() expect(panel()!.querySelector('.sv-tp')).toBeNull() } finally { destroy() } }) })