feat(stammbaum): dismissible accessible mobile bottom sheet (#692)
Wrap the mobile person panel in StammbaumBottomSheet: drag-handle grip with swipe-down-to-dismiss (≥80px), full-screen backdrop button for tap-outside dismiss, role=dialog + aria-label, focus trap, and Escape (NFR-A11Y-004). Pan/zoom state is untouched by open/close (US-PANEL-001/002). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render } from 'vitest-browser-svelte';
|
||||
import StammbaumBottomSheet from './StammbaumBottomSheet.svelte';
|
||||
|
||||
const node = { id: 'p-1', displayName: 'Anna Schmidt', familyMember: true };
|
||||
|
||||
describe('StammbaumBottomSheet (#692)', () => {
|
||||
it('renders as a dialog with the person name as its accessible name', async () => {
|
||||
render(StammbaumBottomSheet, { node, canWrite: false, onClose: () => {} });
|
||||
const dialog = document.querySelector('[role="dialog"]')!;
|
||||
expect(dialog).toBeTruthy();
|
||||
expect(dialog.getAttribute('aria-label')).toBe('Anna Schmidt');
|
||||
});
|
||||
|
||||
it('dismisses on Escape', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumBottomSheet, { node, canWrite: false, onClose });
|
||||
const dialog = document.querySelector('[role="dialog"]') as HTMLElement;
|
||||
dialog.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dismisses when the backdrop is tapped', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumBottomSheet, { node, canWrite: false, onClose });
|
||||
const backdrop = document.querySelector('button[aria-label]') as HTMLButtonElement;
|
||||
backdrop.click();
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dismisses on a downward swipe past the threshold', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(StammbaumBottomSheet, { node, canWrite: false, onClose });
|
||||
const handle = document.querySelector('[role="dialog"] > div') as HTMLElement;
|
||||
handle.dispatchEvent(
|
||||
new PointerEvent('pointerdown', { pointerId: 1, clientY: 100, bubbles: true })
|
||||
);
|
||||
handle.dispatchEvent(
|
||||
new PointerEvent('pointermove', { pointerId: 1, clientY: 220, bubbles: true })
|
||||
);
|
||||
handle.dispatchEvent(
|
||||
new PointerEvent('pointerup', { pointerId: 1, clientY: 220, bubbles: true })
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user