import { describe, it, expect, vi, afterEach } from 'vitest' import { nextTick, ref } from 'vue' import { useDismissableLayer } from '../src/lib/use-dismissable-layer' function firePointerDown(target: EventTarget) { const event = new PointerEvent('pointerdown', { bubbles: true, cancelable: true }) target.dispatchEvent(event) } describe('useDismissableLayer', () => { afterEach(() => { document.body.replaceChildren() }) it('calls onDismiss on Escape keydown while active', () => { const containerRef = ref(document.createElement('div')) const active = ref(true) const onDismiss = vi.fn() useDismissableLayer(containerRef, active, { onDismiss }) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) expect(onDismiss).toHaveBeenCalledOnce() }) it('does not call onDismiss on Escape when inactive', () => { const containerRef = ref(document.createElement('div')) const active = ref(false) const onDismiss = vi.fn() useDismissableLayer(containerRef, active, { onDismiss }) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) expect(onDismiss).not.toHaveBeenCalled() }) it('calls onDismiss on pointerdown outside the container', () => { const container = document.createElement('div') const outside = document.createElement('div') document.body.append(container, outside) const containerRef = ref(container) const active = ref(true) const onDismiss = vi.fn() useDismissableLayer(containerRef, active, { onDismiss }) firePointerDown(outside) expect(onDismiss).toHaveBeenCalledOnce() }) it('does not call onDismiss on pointerdown inside the container', () => { const container = document.createElement('div') const inside = document.createElement('span') container.append(inside) document.body.append(container) const containerRef = ref(container) const active = ref(true) const onDismiss = vi.fn() useDismissableLayer(containerRef, active, { onDismiss }) firePointerDown(inside) expect(onDismiss).not.toHaveBeenCalled() }) it('skips onDismiss when onPointerDownOutside calls preventDefault', () => { const container = document.createElement('div') const outside = document.createElement('div') document.body.append(container, outside) const containerRef = ref(container) const active = ref(true) const onDismiss = vi.fn() useDismissableLayer(containerRef, active, { onDismiss, onPointerDownOutside: (event) => event.preventDefault(), }) firePointerDown(outside) expect(onDismiss).not.toHaveBeenCalled() }) it('only dismisses the topmost layer on Escape when layers are stacked', () => { const container1 = document.createElement('div') const container2 = document.createElement('div') document.body.append(container1, container2) const active1 = ref(true) const active2 = ref(true) const onDismiss1 = vi.fn() const onDismiss2 = vi.fn() useDismissableLayer(ref(container1), active1, { onDismiss: onDismiss1 }) useDismissableLayer(ref(container2), active2, { onDismiss: onDismiss2 }) document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) expect(onDismiss2).toHaveBeenCalledOnce() expect(onDismiss1).not.toHaveBeenCalled() active1.value = false active2.value = false }) it('only dismisses the topmost layer on pointerdown outside it, even inside a lower layer', () => { const container1 = document.createElement('div') const container2 = document.createElement('div') document.body.append(container1, container2) const active1 = ref(true) const active2 = ref(true) const onDismiss1 = vi.fn() const onDismiss2 = vi.fn() useDismissableLayer(ref(container1), active1, { onDismiss: onDismiss1 }) useDismissableLayer(ref(container2), active2, { onDismiss: onDismiss2 }) firePointerDown(container1) expect(onDismiss2).toHaveBeenCalledOnce() expect(onDismiss1).not.toHaveBeenCalled() active1.value = false active2.value = false }) it('promotes the next layer to topmost once the top layer deactivates', async () => { const container1 = document.createElement('div') const container2 = document.createElement('div') document.body.append(container1, container2) const active1 = ref(true) const active2 = ref(true) const onDismiss1 = vi.fn() const onDismiss2 = vi.fn() useDismissableLayer(ref(container1), active1, { onDismiss: onDismiss1 }) useDismissableLayer(ref(container2), active2, { onDismiss: onDismiss2 }) active2.value = false await nextTick() document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) expect(onDismiss1).toHaveBeenCalledOnce() active1.value = false }) })