Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
160 lines
4.2 KiB
TypeScript
160 lines
4.2 KiB
TypeScript
import { act, renderHook } from '@testing-library/react'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { resetCloseOnHistoryBackForTests, useCloseOnHistoryBack } from '@/hooks/useCloseOnHistoryBack'
|
|
|
|
/** Browser has already moved history before `popstate` — dispatch only. */
|
|
const pressBrowserBack = () => {
|
|
act(() => {
|
|
window.dispatchEvent(new PopStateEvent('popstate', { state: window.history.state }))
|
|
})
|
|
}
|
|
|
|
describe('useCloseOnHistoryBack', () => {
|
|
let pushStateSpy: ReturnType<typeof vi.spyOn>
|
|
let backSpy: ReturnType<typeof vi.spyOn>
|
|
|
|
beforeEach(() => {
|
|
resetCloseOnHistoryBackForTests()
|
|
pushStateSpy = vi.spyOn(window.history, 'pushState')
|
|
// jsdom often delays or skips popstate for history.back(); keep ignorePopCount in sync.
|
|
backSpy = vi.spyOn(window.history, 'back').mockImplementation(() => {
|
|
window.dispatchEvent(new PopStateEvent('popstate', { state: window.history.state }))
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
resetCloseOnHistoryBackForTests()
|
|
pushStateSpy.mockRestore()
|
|
backSpy.mockRestore()
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('pushes a history entry when the overlay opens', () => {
|
|
const onRequestClose = vi.fn()
|
|
|
|
renderHook(() => {
|
|
useCloseOnHistoryBack({
|
|
isOpen: true,
|
|
onRequestClose,
|
|
})
|
|
})
|
|
|
|
expect(pushStateSpy).toHaveBeenCalledWith(expect.objectContaining({ consumerModalHistory: expect.any(Number) }), '')
|
|
expect(onRequestClose).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('does not push when enabled is false', () => {
|
|
renderHook(() => {
|
|
useCloseOnHistoryBack({
|
|
isOpen: true,
|
|
enabled: false,
|
|
onRequestClose: vi.fn(),
|
|
})
|
|
})
|
|
|
|
expect(pushStateSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('closes on browser Back without calling history.back again', () => {
|
|
const onRequestClose = vi.fn()
|
|
|
|
renderHook(() => {
|
|
useCloseOnHistoryBack({
|
|
isOpen: true,
|
|
onRequestClose,
|
|
})
|
|
})
|
|
|
|
backSpy.mockClear()
|
|
pressBrowserBack()
|
|
|
|
expect(onRequestClose).toHaveBeenCalledOnce()
|
|
expect(backSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('consumes the history entry on programmatic close', () => {
|
|
const onRequestClose = vi.fn()
|
|
|
|
const { rerender } = renderHook(
|
|
({ isOpen }) => {
|
|
useCloseOnHistoryBack({
|
|
isOpen,
|
|
onRequestClose,
|
|
})
|
|
},
|
|
{ initialProps: { isOpen: true } }
|
|
)
|
|
|
|
backSpy.mockClear()
|
|
|
|
rerender({ isOpen: false })
|
|
|
|
expect(backSpy).toHaveBeenCalledOnce()
|
|
expect(onRequestClose).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('traps Back when allowClose is false by re-pushing history', () => {
|
|
const onRequestClose = vi.fn()
|
|
|
|
renderHook(() => {
|
|
useCloseOnHistoryBack({
|
|
isOpen: true,
|
|
allowClose: false,
|
|
onRequestClose,
|
|
})
|
|
})
|
|
|
|
const pushesAfterOpen = pushStateSpy.mock.calls.length
|
|
|
|
pressBrowserBack()
|
|
|
|
expect(onRequestClose).not.toHaveBeenCalled()
|
|
expect(pushStateSpy.mock.calls.length).toBeGreaterThan(pushesAfterOpen)
|
|
})
|
|
|
|
it('closes only the topmost nested overlay on Back', () => {
|
|
const closeParent = vi.fn()
|
|
const closeChild = vi.fn()
|
|
|
|
renderHook(() => {
|
|
useCloseOnHistoryBack({ isOpen: true, onRequestClose: closeParent })
|
|
useCloseOnHistoryBack({ isOpen: true, onRequestClose: closeChild })
|
|
})
|
|
|
|
pressBrowserBack()
|
|
|
|
expect(closeChild).toHaveBeenCalledOnce()
|
|
expect(closeParent).not.toHaveBeenCalled()
|
|
|
|
pressBrowserBack()
|
|
|
|
expect(closeParent).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('does not leave an orphan entry when the top nested overlay closes programmatically', () => {
|
|
const closeParent = vi.fn()
|
|
const closeChild = vi.fn()
|
|
|
|
const { rerender } = renderHook(
|
|
({ childOpen }) => {
|
|
useCloseOnHistoryBack({ isOpen: true, onRequestClose: closeParent })
|
|
useCloseOnHistoryBack({ isOpen: childOpen, onRequestClose: closeChild })
|
|
},
|
|
{ initialProps: { childOpen: true } }
|
|
)
|
|
|
|
backSpy.mockClear()
|
|
|
|
rerender({ childOpen: false })
|
|
|
|
expect(backSpy).toHaveBeenCalledOnce()
|
|
expect(closeChild).not.toHaveBeenCalled()
|
|
expect(closeParent).not.toHaveBeenCalled()
|
|
|
|
pressBrowserBack()
|
|
|
|
expect(closeParent).toHaveBeenCalledOnce()
|
|
})
|
|
})
|