// @vitest-environment jsdom import { afterEach, describe, expect, it, vi } from 'vitest'; import { createActiveDom } from '$adom'; import { createActiveTimers } from '$timer'; 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 { FloatingProvider } from '$soma/layers/floating'; import { SelectProvider } from './select-provider.svelte'; import type { ScrollAlignment, SelectType } from './types'; 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 timers = createActiveTimers(); const soma = { dom, uix: { timers }, runtime: (morfo: Morfo, sources: Omit) => createSomaRuntime(morfo, { dom, translate: (key) => key, ...sources }) } as unknown as Soma; vi.spyOn(Soma, 'require').mockReturnValue(soma); vi.spyOn(SelectProvider.ctx, 'set').mockImplementation((value) => value); vi.spyOn(FloatingProvider, 'create').mockReturnValue({} as FloatingProvider); return { dom, timers }; } function createSelectOpts() { return { id: state('select-root'), ref: state(null), open: state(true), value: state([]), type: state('single'), disabled: state(false), required: state(false), name: state('choice'), loop: state(false), allowDeselect: state(false), scrollAlignment: state('nearest'), dir: state<'ltr' | 'rtl'>('ltr'), onOpenChangeComplete: state(() => {}) }; } describe('SelectProvider', () => { afterEach(() => { vi.restoreAllMocks(); document.body.innerHTML = ''; }); it('selects, closes and resolves labels in single-select mode', () => { const { dom, timers } = installSomaHarness(); const opts = createSelectOpts(); const { result: provider, cleanup } = withEffectRoot(() => SelectProvider.create(opts)); provider.registerLabel('a', 'Alpha'); provider.selectItem('a'); expect(opts.value.current).toEqual(['a']); expect(opts.open.current).toBe(false); expect(provider.getDisplayText()).toBe('Alpha'); opts.open.current = true; opts.allowDeselect.current = true; provider.selectItem('a'); expect(opts.value.current).toEqual([]); expect(opts.open.current).toBe(false); cleanup(); timers.dispose(); dom.dispose(); }); it('toggles values without closing in multiple-select mode', () => { const { dom, timers } = installSomaHarness(); const opts = createSelectOpts(); opts.type.current = 'multiple'; opts.value.current = ['a']; const { result: provider, cleanup } = withEffectRoot(() => SelectProvider.create(opts)); provider.selectItem('b'); expect(opts.value.current).toEqual(['a', 'b']); expect(opts.open.current).toBe(true); provider.selectItem('a'); expect(opts.value.current).toEqual(['b']); expect(opts.open.current).toBe(true); cleanup(); timers.dispose(); dom.dispose(); }); it('filters disabled DOM items from navigation candidates', () => { const { dom, timers } = installSomaHarness(); const opts = createSelectOpts(); const container = document.createElement('div'); container.innerHTML = `
`; const { result: provider, cleanup } = withEffectRoot(() => SelectProvider.create(opts)); expect(provider.getItems(container).map((el) => el.id)).toEqual(['a', 'c']); cleanup(); timers.dispose(); dom.dispose(); }); });