// @vitest-environment nuxt import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { useOverlayStack } from '../use-overlay-stack'; const NO_ENTER = '--no-enter-anim'; const NO_LEAVE = '--no-leave-anim'; let frames: Array<() => void> = []; function flushFrames() { const queued = frames; frames = []; queued.forEach((cb) => cb()); } function createOverlay() { const el = document.createElement('dialog'); document.body.appendChild(el); return el; } describe('useOverlayStack', () => { beforeEach(() => { frames = []; vi.stubGlobal('requestAnimationFrame', (cb: () => void) => { frames.push(cb); return 0; }); }); afterEach(() => { flushFrames(); document.body.innerHTML = ''; vi.unstubAllGlobals(); }); it('первый оверлей анимируется в обе стороны', () => { const { registerOverlay } = useOverlayStack(); const el = createOverlay(); registerOverlay(el); expect(el.classList.contains(NO_ENTER)).toBe(false); expect(el.classList.contains(NO_LEAVE)).toBe(false); }); it('второй оверлей открывается без анимации появления', () => { const { registerOverlay } = useOverlayStack(); const first = createOverlay(); const second = createOverlay(); registerOverlay(first); registerOverlay(second); expect(second.classList.contains(NO_ENTER)).toBe(true); }); it('пока оверлеев больше одного, закрытие не анимируется ни у одного', () => { const { registerOverlay } = useOverlayStack(); const first = createOverlay(); const second = createOverlay(); registerOverlay(first); registerOverlay(second); expect(first.classList.contains(NO_LEAVE)).toBe(true); expect(second.classList.contains(NO_LEAVE)).toBe(true); }); it('уходящий оверлей остаётся в стеке до конца кадра — сменщик успевает открыться без анимации', () => { const { registerOverlay, releaseOverlay } = useOverlayStack(); const closing = createOverlay(); const opening = createOverlay(); registerOverlay(closing); releaseOverlay(closing); registerOverlay(opening); expect(opening.classList.contains(NO_ENTER)).toBe(true); expect(closing.classList.contains(NO_LEAVE)).toBe(true); }); it('оставшийся последним снова закрывается с анимацией', () => { const { registerOverlay, releaseOverlay } = useOverlayStack(); const closing = createOverlay(); const opening = createOverlay(); registerOverlay(closing); releaseOverlay(closing); registerOverlay(opening); flushFrames(); expect(opening.classList.contains(NO_LEAVE)).toBe(false); }); it('оверлей, удалённый из DOM без релиза, не блокирует анимации следующему', () => { const { registerOverlay } = useOverlayStack(); const leaked = createOverlay(); const next = createOverlay(); registerOverlay(leaked); leaked.remove(); registerOverlay(next); expect(next.classList.contains(NO_ENTER)).toBe(false); expect(next.classList.contains(NO_LEAVE)).toBe(false); }); });