// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest'; import { createActiveDom } from '$adom'; import { CalendarDate } from '$libs/days'; import type { DateMatcher, DateOnInvalid, DateRange, DateRangeValidator, DateValue, EditableSegmentPart, Granularity, HourCycle, WeekStartsOn } from '$libs/days'; import { state } from '$libs/reactive'; import type { Morfo } from '$uix/morfo'; import { Soma } from '$soma/core/soma.svelte'; import { createSomaRuntime, type SomaRuntimeSources } from '$soma/runtime.svelte'; import type { Direction, OnChangeFn } from '../../types'; import { DateRangePickerProvider } from './date-range-picker-provider.svelte'; function withEffectRoot(fn: () => T): { result: T; cleanup: () => void } { let result!: T; const cleanup = $effect.root(() => { result = fn(); }); return { result, cleanup }; } function installSomaHarness() { const dom = createActiveDom(); const soma = { dom, langs: { getLocale: () => 'en-US', ts: (key: string) => key }, runtime: (morfo: Morfo, sources: Omit) => createSomaRuntime(morfo, { dom, translate: (key) => key, ...sources }) } as unknown as Soma; vi.spyOn(Soma, 'require').mockReturnValue(soma); vi.spyOn(DateRangePickerProvider.ctx, 'set').mockImplementation((value) => value); return { dom }; } function createDateRangePickerOpts() { const placeholder = new CalendarDate(2026, 5, 15); return { id: state('date-range-picker-root'), ref: state(null), value: state({ start: undefined, end: undefined }), placeholder: state(placeholder), startValue: state(undefined), endValue: state(undefined), open: state(true), validate: state(undefined), onInvalid: state(undefined), minValue: state(undefined), maxValue: state(undefined), minDays: state(undefined), maxDays: state(undefined), allowSingleDay: state(true), isDateDisabled: state(undefined), isDateUnavailable: state(undefined), isDateHoliday: state(undefined), disabled: state(false), readonly: state(false), startReadonlySegments: state([]), endReadonlySegments: state([]), required: state(false), closeOnRangeSelect: state(true), mode: state<'inline' | 'modal'>('inline'), kind: state<'date' | 'month' | 'year'>('date'), granularity: state(undefined), hideTimeZone: state(false), hourCycle: state(undefined), locale: state('en-US'), dir: state('ltr'), pagedNavigation: state(false), weekStartsOn: state(undefined), weekdayFormat: state('narrow'), fixedWeeks: state(false), numberOfMonths: state(1), calendarLabel: state(undefined), disableDaysOutsideMonth: state(false), preventDeselect: state(false), initialFocus: state(false), monthFormat: state string)>('long'), yearFormat: state string)>('numeric'), showWeekNumbers: state(false), weekNumberFormat: state<'iso' | 'simple'>('iso'), announceNavigation: state(true), errorMessageId: state(undefined), onOpenChangeComplete: state>(() => {}) }; } describe('DateRangePickerProvider', () => { afterEach(() => { vi.restoreAllMocks(); document.body.innerHTML = ''; }); it('registers the provider part through SomaRuntime', () => { const { dom } = installSomaHarness(); const opts = createDateRangePickerOpts(); const { result: provider, cleanup } = withEffectRoot(() => DateRangePickerProvider.create(opts) ); const props = provider.props as Record; expect(provider.runtimePart.props.id).toBe('date-range-picker-root'); expect(props.id).toBe('date-range-picker-root'); expect(props['data-date-range-picker']).toBe(''); expect(props['data-state']).toBe('open'); cleanup(); dom.dispose(); }); it('exposes root state flags for composed wrappers', () => { const { dom } = installSomaHarness(); const opts = createDateRangePickerOpts(); const { result: provider, cleanup } = withEffectRoot(() => DateRangePickerProvider.create(opts) ); opts.open.current = false; opts.disabled.current = true; opts.readonly.current = true; opts.required.current = true; opts.value.current = { start: new CalendarDate(2026, 4, 30), end: new CalendarDate(2026, 5, 2) }; opts.minValue.current = new CalendarDate(2026, 5, 1); const props = provider.props as Record; expect(props['data-state']).toBe('closed'); expect(props['data-disabled']).toBe(''); expect(props['data-readonly']).toBe(''); expect(props['data-required']).toBe(''); expect(props['data-invalid']).toBe(''); cleanup(); dom.dispose(); }); it('closes the popover only when closeOnRangeSelect is enabled', () => { const { dom } = installSomaHarness(); const opts = createDateRangePickerOpts(); const { result: provider, cleanup } = withEffectRoot(() => DateRangePickerProvider.create(opts) ); provider.handleRangeSelect(); expect(opts.open.current).toBe(false); opts.open.current = true; opts.closeOnRangeSelect.current = false; provider.handleRangeSelect(); expect(opts.open.current).toBe(true); cleanup(); dom.dispose(); }); it('validates single-day and range length constraints', () => { const { dom } = installSomaHarness(); const opts = createDateRangePickerOpts(); const start = new CalendarDate(2026, 5, 10); const { result: provider, cleanup } = withEffectRoot(() => DateRangePickerProvider.create(opts) ); opts.allowSingleDay.current = false; opts.value.current = { start, end: start }; expect(provider.validationStatus).toEqual({ reason: 'single-day' }); expect(provider.props['data-invalid']).toBe(''); opts.allowSingleDay.current = true; opts.minDays.current = 3; opts.value.current = { start, end: new CalendarDate(2026, 5, 11) }; expect(provider.validationStatus).toEqual({ reason: 'min-days' }); opts.minDays.current = undefined; opts.maxDays.current = 3; opts.value.current = { start, end: new CalendarDate(2026, 5, 20) }; expect(provider.validationStatus).toEqual({ reason: 'max-days' }); cleanup(); dom.dispose(); }); });