parent
802ebce655
commit
c6d24eeea3
@ -0,0 +1,68 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { CalendarDate, CalendarDateTime } from '$libs/days';
|
||||||
|
|
||||||
|
import { resolveInitialCalendarPlaceholder } from './calendar-provider.svelte';
|
||||||
|
import {
|
||||||
|
applyMultipleSelection,
|
||||||
|
applySingleSelection,
|
||||||
|
computeDateFlags,
|
||||||
|
createMonths
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
describe('CalendarProvider helpers', () => {
|
||||||
|
it('resolves the initial placeholder from selection before falling back to today', () => {
|
||||||
|
const selected = new CalendarDate(2026, 5, 15);
|
||||||
|
|
||||||
|
expect(resolveInitialCalendarPlaceholder(undefined, selected, 'single')).toBe(selected);
|
||||||
|
expect(resolveInitialCalendarPlaceholder(undefined, [selected], 'multiple')).toBe(selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates fixed six-week month grids', () => {
|
||||||
|
const months = createMonths(new CalendarDate(2026, 2, 1), {
|
||||||
|
numberOfMonths: 2,
|
||||||
|
weekStartsOn: 1,
|
||||||
|
fixedWeeks: true,
|
||||||
|
locale: 'es-ES'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(months).toHaveLength(2);
|
||||||
|
expect(months[0].dates).toHaveLength(42);
|
||||||
|
expect(months[0].weeks).toHaveLength(6);
|
||||||
|
expect(months[1].dates).toHaveLength(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves time when single selection changes the calendar date', () => {
|
||||||
|
const previous = new CalendarDateTime(2026, 5, 15, 13, 45, 30);
|
||||||
|
const next = applySingleSelection(previous, new CalendarDate(2026, 6, 1), false);
|
||||||
|
|
||||||
|
expect(next).toBeInstanceOf(CalendarDateTime);
|
||||||
|
expect((next as CalendarDateTime).hour).toBe(13);
|
||||||
|
expect((next as CalendarDateTime).minute).toBe(45);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggles multiple selection and resets when maxDays is exceeded', () => {
|
||||||
|
const first = new CalendarDate(2026, 5, 1);
|
||||||
|
const second = new CalendarDate(2026, 5, 2);
|
||||||
|
const third = new CalendarDate(2026, 5, 3);
|
||||||
|
|
||||||
|
expect(applyMultipleSelection([first], first, false, undefined)).toEqual([]);
|
||||||
|
expect(applyMultipleSelection([first, second], third, false, 2)).toEqual([third]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('composes disabled, unavailable and holiday flags independently', () => {
|
||||||
|
const day = new CalendarDate(2026, 5, 15);
|
||||||
|
const flags = computeDateFlags(day, {
|
||||||
|
minValue: new CalendarDate(2026, 5, 1),
|
||||||
|
maxValue: new CalendarDate(2026, 5, 31),
|
||||||
|
disabled: false,
|
||||||
|
isDateDisabled: undefined,
|
||||||
|
isDateUnavailable: (date) => date.day === 15,
|
||||||
|
isDateHoliday: (date) => date.day === 15
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(flags).toEqual({ disabled: false, unavailable: true, holiday: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { flushSync } from 'svelte';
|
||||||
|
|
||||||
|
import { createActiveDom } from '$adom';
|
||||||
|
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 { CommandProvider } from './command-provider.svelte';
|
||||||
|
import { computeCommandScore } from './score';
|
||||||
|
|
||||||
|
function withEffectRoot<T>(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,
|
||||||
|
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
|
||||||
|
createSomaRuntime(morfo, {
|
||||||
|
dom,
|
||||||
|
translate: (key) => key,
|
||||||
|
...sources
|
||||||
|
})
|
||||||
|
} as unknown as Soma;
|
||||||
|
|
||||||
|
vi.spyOn(Soma, 'require').mockReturnValue(soma);
|
||||||
|
vi.spyOn(CommandProvider.ctx, 'set').mockImplementation((value) => value);
|
||||||
|
|
||||||
|
return { dom };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCommandOpts() {
|
||||||
|
return {
|
||||||
|
id: state('command-root'),
|
||||||
|
ref: state<HTMLElement | null>(null),
|
||||||
|
value: state(''),
|
||||||
|
search: state(''),
|
||||||
|
label: state<string | undefined>('Commands'),
|
||||||
|
shouldFilter: state(true),
|
||||||
|
filter: state(computeCommandScore),
|
||||||
|
loop: state(false),
|
||||||
|
vimBindings: state(false),
|
||||||
|
columns: state(1),
|
||||||
|
disablePointerSelection: state(false),
|
||||||
|
dir: state<'ltr' | 'rtl'>('ltr')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function item(value: string, patch: Partial<Parameters<CommandProvider['registerItem']>[0]> = {}) {
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
keywords: [],
|
||||||
|
forceMount: false,
|
||||||
|
disabled: false,
|
||||||
|
groupValue: undefined,
|
||||||
|
element: null,
|
||||||
|
score: 1,
|
||||||
|
...patch
|
||||||
|
} as Parameters<CommandProvider['registerItem']>[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CommandProvider', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters visible items and auto-selects the first surviving value', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createCommandOpts();
|
||||||
|
opts.search.current = 'op';
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => CommandProvider.create(opts));
|
||||||
|
provider.registerItem(item('Open'));
|
||||||
|
provider.registerItem(item('Save'));
|
||||||
|
provider.registerItem(item('Hidden', { forceMount: true }));
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
expect(provider.visibleOrderedValues).toEqual(['Open', 'Hidden']);
|
||||||
|
expect(provider.isVisible('Save')).toBe(false);
|
||||||
|
expect(opts.value.current).toBe('Open');
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('navigates through visible values and honours disabled selection handlers', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createCommandOpts();
|
||||||
|
opts.loop.current = true;
|
||||||
|
const onSelect = vi.fn();
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => CommandProvider.create(opts));
|
||||||
|
provider.registerItem(item('Open'));
|
||||||
|
provider.registerItem(item('Close', { disabled: true }));
|
||||||
|
provider.registerOnSelect('Open', onSelect);
|
||||||
|
provider.registerOnSelect('Close', onSelect);
|
||||||
|
flushSync();
|
||||||
|
|
||||||
|
provider.last();
|
||||||
|
expect(opts.value.current).toBe('Close');
|
||||||
|
provider.selectCurrent();
|
||||||
|
expect(onSelect).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
provider.next();
|
||||||
|
expect(opts.value.current).toBe('Open');
|
||||||
|
provider.selectCurrent();
|
||||||
|
expect(onSelect).toHaveBeenCalledWith('Open');
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createActiveDom } from '$adom';
|
||||||
|
import { CalendarDate } from '$libs/days';
|
||||||
|
import {
|
||||||
|
areAllSegmentsFilledDOM,
|
||||||
|
getFirstSegment,
|
||||||
|
getValueFromSegmentsDOM,
|
||||||
|
handleSegmentNavigation,
|
||||||
|
initSegmentStates,
|
||||||
|
isAcceptableSegmentKey
|
||||||
|
} from '$soma/datetime';
|
||||||
|
|
||||||
|
function createField() {
|
||||||
|
const field = document.createElement('div');
|
||||||
|
for (const [id, part, text] of [
|
||||||
|
['month', 'month', '05'],
|
||||||
|
['literal', 'literal', '/'],
|
||||||
|
['day', 'day', '15'],
|
||||||
|
['year', 'year', '2026']
|
||||||
|
] as const) {
|
||||||
|
const segment = document.createElement('span');
|
||||||
|
segment.id = id;
|
||||||
|
segment.dataset.segment = part;
|
||||||
|
segment.textContent = text;
|
||||||
|
field.appendChild(segment);
|
||||||
|
}
|
||||||
|
document.body.appendChild(field);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DateField segment helpers', () => {
|
||||||
|
it('creates fresh segment state maps for each field instance', () => {
|
||||||
|
const first = initSegmentStates();
|
||||||
|
const second = initSegmentStates();
|
||||||
|
|
||||||
|
first.day.updating = '1';
|
||||||
|
|
||||||
|
expect(second.day.updating).toBeNull();
|
||||||
|
expect(second.month.hasLeftFocus).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts numeric input and navigation keys only', () => {
|
||||||
|
expect(isAcceptableSegmentKey('7')).toBe(true);
|
||||||
|
expect(isAcceptableSegmentKey('ArrowRight')).toBe(true);
|
||||||
|
expect(isAcceptableSegmentKey('x')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reads only rendered editable segments from the DOM', () => {
|
||||||
|
const field = createField();
|
||||||
|
|
||||||
|
expect(getFirstSegment(field)?.id).toBe('month');
|
||||||
|
expect(areAllSegmentsFilledDOM({ month: '05', day: '15', year: '2026' }, field)).toBe(true);
|
||||||
|
|
||||||
|
const value = getValueFromSegmentsDOM({
|
||||||
|
segmentObj: { month: '05', day: '15', year: '2026' },
|
||||||
|
fieldNode: field,
|
||||||
|
dateRef: new CalendarDate(2026, 1, 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(value.month).toBe(5);
|
||||||
|
expect(value.day).toBe(15);
|
||||||
|
expect(value.year).toBe(2026);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('moves focus across segments through ActiveDom', () => {
|
||||||
|
const dom = createActiveDom();
|
||||||
|
const field = createField();
|
||||||
|
const month = field.querySelector<HTMLElement>('#month')!;
|
||||||
|
const focus = vi.spyOn(dom, 'focus');
|
||||||
|
const event = new KeyboardEvent('keydown', { key: 'ArrowRight' });
|
||||||
|
Object.defineProperty(event, 'currentTarget', { value: month });
|
||||||
|
|
||||||
|
handleSegmentNavigation(dom, event, field);
|
||||||
|
|
||||||
|
expect(focus).toHaveBeenCalledWith(field.querySelector('#day'));
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createActiveDom } from '$adom';
|
||||||
|
import { state } from '$libs/reactive';
|
||||||
|
import type { Morfo } from '$uix/morfo';
|
||||||
|
import { Soma } from '$soma/core/soma.svelte';
|
||||||
|
import {
|
||||||
|
createSomaRuntime,
|
||||||
|
type EventEngineEmitter,
|
||||||
|
type SomaRuntimeSources
|
||||||
|
} from '$soma/runtime.svelte';
|
||||||
|
|
||||||
|
import { DialogProvider } from './dialog-provider.svelte';
|
||||||
|
|
||||||
|
function withEffectRoot<T>(fn: () => T): { result: T; cleanup: () => void } {
|
||||||
|
let result!: T;
|
||||||
|
const cleanup = $effect.root(() => {
|
||||||
|
result = fn();
|
||||||
|
});
|
||||||
|
return { result, cleanup };
|
||||||
|
}
|
||||||
|
|
||||||
|
function installSomaHarness() {
|
||||||
|
const dom = createActiveDom();
|
||||||
|
const eventEngine = { emit: vi.fn(async () => {}) } satisfies EventEngineEmitter;
|
||||||
|
const soma = {
|
||||||
|
dom,
|
||||||
|
events: eventEngine,
|
||||||
|
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
|
||||||
|
createSomaRuntime(morfo, {
|
||||||
|
dom,
|
||||||
|
eventEngine,
|
||||||
|
translate: (key) => key,
|
||||||
|
...sources
|
||||||
|
})
|
||||||
|
} as unknown as Soma;
|
||||||
|
|
||||||
|
vi.spyOn(Soma, 'require').mockReturnValue(soma);
|
||||||
|
vi.spyOn(DialogProvider.ctx, 'getOr').mockReturnValue(undefined);
|
||||||
|
vi.spyOn(DialogProvider.ctx, 'set').mockImplementation((value) => value);
|
||||||
|
|
||||||
|
return { dom, eventEngine };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDialogOpts() {
|
||||||
|
return {
|
||||||
|
id: state('dialog-root'),
|
||||||
|
ref: state<HTMLElement | null>(null),
|
||||||
|
open: state(false),
|
||||||
|
disabled: state(false),
|
||||||
|
modal: state(true),
|
||||||
|
variant: state<'dialog' | 'alertdialog'>('dialog'),
|
||||||
|
intent: state<'neutral' | 'risk' | 'threat'>('neutral'),
|
||||||
|
onOpenChangeComplete: state(() => {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DialogProvider', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens and closes through the morfo runtime without a registered content part', async () => {
|
||||||
|
const { dom, eventEngine } = installSomaHarness();
|
||||||
|
const opts = createDialogOpts();
|
||||||
|
const fallbackTarget = document.createElement('div');
|
||||||
|
document.body.appendChild(fallbackTarget);
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => {
|
||||||
|
const p = DialogProvider.create(opts);
|
||||||
|
p.contentId.current = 'dialog-content';
|
||||||
|
p.runtime.part('content', {
|
||||||
|
id: state('dialog-content'),
|
||||||
|
ref: state<HTMLElement | null>(fallbackTarget),
|
||||||
|
syncAttrs: true
|
||||||
|
});
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
provider.handleOpen();
|
||||||
|
expect(opts.open.current).toBe(true);
|
||||||
|
|
||||||
|
await provider.triggerClose('close-dismiss', fallbackTarget);
|
||||||
|
|
||||||
|
expect(opts.open.current).toBe(false);
|
||||||
|
expect(fallbackTarget.getAttribute('data-last-action')).toBe('dismissed');
|
||||||
|
expect(fallbackTarget.getAttribute('data-state')).toBe('closed');
|
||||||
|
expect(eventEngine.emit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'close-dismiss',
|
||||||
|
family: 'emerge',
|
||||||
|
target: fallbackTarget
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not mutate open state while disabled', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createDialogOpts();
|
||||||
|
opts.disabled.current = true;
|
||||||
|
opts.open.current = false;
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => DialogProvider.create(opts));
|
||||||
|
|
||||||
|
provider.handleOpen();
|
||||||
|
expect(opts.open.current).toBe(false);
|
||||||
|
|
||||||
|
opts.open.current = true;
|
||||||
|
provider.handleClose();
|
||||||
|
expect(opts.open.current).toBe(true);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,151 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createActiveDom } from '$adom';
|
||||||
|
import { state } from '$libs/reactive';
|
||||||
|
import type { Morfo } from '$uix/morfo';
|
||||||
|
import { Soma } from '$soma/core/soma.svelte';
|
||||||
|
import {
|
||||||
|
createSomaRuntime,
|
||||||
|
type EventEngineEmitter,
|
||||||
|
type SomaRuntimeSources
|
||||||
|
} from '$soma/runtime.svelte';
|
||||||
|
|
||||||
|
import { DrawerProvider } from './drawer-provider.svelte';
|
||||||
|
import type { DrawerDirection, DrawerSnapPoint, DrawerVariant } from './types';
|
||||||
|
|
||||||
|
function withEffectRoot<T>(fn: () => T): { result: T; cleanup: () => void } {
|
||||||
|
let result!: T;
|
||||||
|
const cleanup = $effect.root(() => {
|
||||||
|
result = fn();
|
||||||
|
});
|
||||||
|
return { result, cleanup };
|
||||||
|
}
|
||||||
|
|
||||||
|
function installSomaHarness(dir: 'ltr' | 'rtl' = 'ltr') {
|
||||||
|
const dom = createActiveDom();
|
||||||
|
const eventEngine = { emit: vi.fn(async () => {}) } satisfies EventEngineEmitter;
|
||||||
|
const soma = {
|
||||||
|
dom,
|
||||||
|
events: eventEngine,
|
||||||
|
prefs: { getDir: () => dir },
|
||||||
|
runtime: (morfo: Morfo, sources: Omit<SomaRuntimeSources, 'dom' | 'eventEngine'>) =>
|
||||||
|
createSomaRuntime(morfo, {
|
||||||
|
dom,
|
||||||
|
eventEngine,
|
||||||
|
translate: (key) => key,
|
||||||
|
...sources
|
||||||
|
})
|
||||||
|
} as unknown as Soma;
|
||||||
|
|
||||||
|
vi.spyOn(Soma, 'require').mockReturnValue(soma);
|
||||||
|
vi.spyOn(DrawerProvider.ctx, 'getOr').mockReturnValue(undefined);
|
||||||
|
vi.spyOn(DrawerProvider.ctx, 'set').mockImplementation((value) => value);
|
||||||
|
|
||||||
|
return { dom, eventEngine };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDrawerOpts() {
|
||||||
|
return {
|
||||||
|
id: state('drawer-root'),
|
||||||
|
ref: state<HTMLElement | null>(null),
|
||||||
|
open: state(false),
|
||||||
|
activeSnapPoint: state<DrawerSnapPoint | null>(null),
|
||||||
|
isDragging: state(false),
|
||||||
|
disabled: state(false),
|
||||||
|
variant: state<DrawerVariant>('overlay'),
|
||||||
|
dismissible: state(true),
|
||||||
|
handleOnly: state(false),
|
||||||
|
dragToDismiss: state(false),
|
||||||
|
direction: state<DrawerDirection>('bottom'),
|
||||||
|
snapPoints: state<DrawerSnapPoint[] | undefined>(undefined),
|
||||||
|
snapToSequential: state(false),
|
||||||
|
closeThreshold: state(0.25),
|
||||||
|
velocityThreshold: state(0.5),
|
||||||
|
scrollLockTimeout: state(500),
|
||||||
|
minSize: state<number | string | undefined>(undefined),
|
||||||
|
maxSize: state<number | string | undefined>(undefined),
|
||||||
|
onOpenChangeComplete: state(() => {}),
|
||||||
|
onDragStart: state<((event: PointerEvent) => void) | undefined>(undefined),
|
||||||
|
onDrag: state<((event: PointerEvent, progress: number) => void) | undefined>(undefined),
|
||||||
|
onRelease: state<((event: PointerEvent, willClose: boolean) => void) | undefined>(undefined),
|
||||||
|
onActiveSnapPointChange: state(() => {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DrawerProvider', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves logical directions from the active UI direction preference', () => {
|
||||||
|
const { dom } = installSomaHarness('rtl');
|
||||||
|
const opts = createDrawerOpts();
|
||||||
|
opts.direction.current = 'start';
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => DrawerProvider.create(opts));
|
||||||
|
|
||||||
|
expect(provider.resolvedSide).toBe('right');
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dismisses through the morfo runtime and clears transient snap state', async () => {
|
||||||
|
const { dom, eventEngine } = installSomaHarness();
|
||||||
|
const opts = createDrawerOpts();
|
||||||
|
opts.open.current = true;
|
||||||
|
opts.activeSnapPoint.current = 0.5;
|
||||||
|
const fallbackTarget = document.createElement('div');
|
||||||
|
document.body.appendChild(fallbackTarget);
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => {
|
||||||
|
const p = DrawerProvider.create(opts);
|
||||||
|
p.contentId.current = 'drawer-content';
|
||||||
|
p.runtime.part('content', {
|
||||||
|
id: state('drawer-content'),
|
||||||
|
ref: state<HTMLElement | null>(fallbackTarget),
|
||||||
|
syncAttrs: true
|
||||||
|
});
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.triggerClose('close-dismiss', fallbackTarget);
|
||||||
|
|
||||||
|
expect(opts.open.current).toBe(false);
|
||||||
|
expect(opts.activeSnapPoint.current).toBeNull();
|
||||||
|
expect(fallbackTarget.getAttribute('data-last-action')).toBe('dismissed');
|
||||||
|
expect(fallbackTarget.getAttribute('data-state')).toBe('closed');
|
||||||
|
expect(eventEngine.emit).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'close-dismiss',
|
||||||
|
family: 'emerge',
|
||||||
|
target: fallbackTarget
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps persistent drawers structurally open', () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const opts = createDrawerOpts();
|
||||||
|
opts.variant.current = 'persistent';
|
||||||
|
opts.open.current = false;
|
||||||
|
|
||||||
|
const { result: provider, cleanup } = withEffectRoot(() => DrawerProvider.create(opts));
|
||||||
|
|
||||||
|
provider.handleOpen();
|
||||||
|
expect(opts.open.current).toBe(false);
|
||||||
|
|
||||||
|
opts.open.current = true;
|
||||||
|
provider.handleClose();
|
||||||
|
expect(opts.open.current).toBe(true);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in new issue