parent
3ceb94619a
commit
fe61f5be9b
@ -0,0 +1,134 @@
|
|||||||
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
|
import { tick } from 'svelte';
|
||||||
|
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 SomaRuntimeSources } from '$soma/runtime.svelte';
|
||||||
|
import { DialogProvider } from '$soma/components/dialog/dialog-provider.svelte';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AlertDialogActionProvider,
|
||||||
|
AlertDialogCancelProvider
|
||||||
|
} from './alert-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 soma = {
|
||||||
|
dom,
|
||||||
|
langs: {
|
||||||
|
ts: vi.fn((key: string) => (key.includes('cancel') ? 'Cancel' : 'Confirm'))
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
|
||||||
|
return { dom };
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionOpts(el: HTMLButtonElement, ariaLabel: string | undefined = undefined) {
|
||||||
|
return {
|
||||||
|
id: state('alert-action'),
|
||||||
|
ref: state<HTMLElement | null>(el),
|
||||||
|
ariaLabel: state(ariaLabel)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelOpts(el: HTMLButtonElement, ariaLabel: string | undefined = undefined) {
|
||||||
|
return {
|
||||||
|
id: state('alert-cancel'),
|
||||||
|
ref: state<HTMLElement | null>(el),
|
||||||
|
ariaLabel: state(ariaLabel)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AlertDialogProvider parts', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
document.body.innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('projects action/cancel contracts and closes the backing Dialog', async () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
const handleClose = vi.fn();
|
||||||
|
vi.spyOn(DialogProvider, 'require').mockReturnValue({
|
||||||
|
handleClose
|
||||||
|
} as unknown as DialogProvider);
|
||||||
|
const actionEl = document.createElement('button');
|
||||||
|
const cancelEl = document.createElement('button');
|
||||||
|
document.body.append(actionEl, cancelEl);
|
||||||
|
const actionOptions = actionOpts(actionEl);
|
||||||
|
const cancelOptions = cancelOpts(cancelEl, 'Keep item');
|
||||||
|
|
||||||
|
const { result, cleanup } = withEffectRoot(() => {
|
||||||
|
const action = AlertDialogActionProvider.create(actionOptions);
|
||||||
|
const cancel = AlertDialogCancelProvider.create(cancelOptions);
|
||||||
|
return { action, cancel };
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.action.resolvedAriaLabel).toBe('Confirm');
|
||||||
|
expect(result.cancel.resolvedAriaLabel).toBe('Keep item');
|
||||||
|
expect(result.action.props).toMatchObject({
|
||||||
|
'data-alert-dialog-action': '',
|
||||||
|
onclick: result.action.onclick
|
||||||
|
});
|
||||||
|
expect(result.cancel.props).toMatchObject({
|
||||||
|
'data-alert-dialog-cancel': '',
|
||||||
|
onclick: result.cancel.onclick
|
||||||
|
});
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
expect(actionEl.getAttribute('type')).toBe('button');
|
||||||
|
expect(actionEl.getAttribute('aria-label')).toBe('Confirm');
|
||||||
|
expect(cancelEl.getAttribute('type')).toBe('button');
|
||||||
|
expect(cancelEl.getAttribute('aria-label')).toBe('Keep item');
|
||||||
|
|
||||||
|
result.action.onclick({ currentTarget: actionEl } as never);
|
||||||
|
result.cancel.onclick({ currentTarget: cancelEl } as never);
|
||||||
|
expect(handleClose).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps aria-label reactive to explicit overrides', async () => {
|
||||||
|
const { dom } = installSomaHarness();
|
||||||
|
vi.spyOn(DialogProvider, 'require').mockReturnValue({
|
||||||
|
handleClose: vi.fn()
|
||||||
|
} as unknown as DialogProvider);
|
||||||
|
const actionEl = document.createElement('button');
|
||||||
|
document.body.appendChild(actionEl);
|
||||||
|
const opts = actionOpts(actionEl);
|
||||||
|
|
||||||
|
const { result: action, cleanup } = withEffectRoot(() =>
|
||||||
|
AlertDialogActionProvider.create(opts)
|
||||||
|
);
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
expect(actionEl.getAttribute('aria-label')).toBe('Confirm');
|
||||||
|
opts.ariaLabel.current = 'Delete account';
|
||||||
|
await tick();
|
||||||
|
expect(action.resolvedAriaLabel).toBe('Delete account');
|
||||||
|
expect(actionEl.getAttribute('aria-label')).toBe('Delete account');
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
dom.dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in new issue