useConfirmOnClose.test.tsx153 lines · main
1import { act, renderHook } from '@testing-library/react'
2import { describe, expect, it, vi } from 'vitest'
3
4import { useConfirmOnClose } from './useConfirmOnClose'
5
6describe('useConfirmOnClose', () => {
7 it('closes immediately when the form is clean', () => {
8 const onClose = vi.fn()
9
10 const { result } = renderHook(() =>
11 useConfirmOnClose({
12 checkIsDirty: () => false,
13 onClose,
14 })
15 )
16
17 act(() => {
18 result.current.confirmOnClose()
19 })
20
21 expect(onClose).toHaveBeenCalledTimes(1)
22 expect(result.current.modalProps.visible).toBe(false)
23 })
24
25 it('opens the confirmation modal when the form is dirty', () => {
26 const onClose = vi.fn()
27
28 const { result } = renderHook(() =>
29 useConfirmOnClose({
30 checkIsDirty: () => true,
31 onClose,
32 })
33 )
34
35 act(() => {
36 result.current.confirmOnClose()
37 })
38
39 expect(onClose).not.toHaveBeenCalled()
40 expect(result.current.modalProps.visible).toBe(true)
41 })
42
43 it('ignores open events and handles close events via handleOpenChange', () => {
44 const onClose = vi.fn()
45
46 const { result } = renderHook(() =>
47 useConfirmOnClose({
48 checkIsDirty: () => true,
49 onClose,
50 })
51 )
52
53 act(() => {
54 result.current.handleOpenChange(true)
55 })
56
57 expect(onClose).not.toHaveBeenCalled()
58 expect(result.current.modalProps.visible).toBe(false)
59
60 act(() => {
61 result.current.handleOpenChange(false)
62 })
63
64 expect(onClose).not.toHaveBeenCalled()
65 expect(result.current.modalProps.visible).toBe(true)
66 })
67
68 it('confirms and closes after the discard modal is accepted', () => {
69 const onClose = vi.fn()
70
71 const { result } = renderHook(() =>
72 useConfirmOnClose({
73 checkIsDirty: () => true,
74 onClose,
75 })
76 )
77
78 act(() => {
79 result.current.confirmOnClose()
80 })
81
82 expect(result.current.modalProps.visible).toBe(true)
83
84 act(() => {
85 result.current.modalProps.onClose()
86 })
87
88 expect(onClose).toHaveBeenCalledTimes(1)
89 expect(result.current.modalProps.visible).toBe(false)
90 })
91
92 it('cancels and keeps the form open after the discard modal is dismissed', () => {
93 const onClose = vi.fn()
94
95 const { result } = renderHook(() =>
96 useConfirmOnClose({
97 checkIsDirty: () => true,
98 onClose,
99 })
100 )
101
102 act(() => {
103 result.current.confirmOnClose()
104 })
105
106 expect(result.current.modalProps.visible).toBe(true)
107
108 act(() => {
109 result.current.modalProps.onCancel()
110 })
111
112 expect(onClose).not.toHaveBeenCalled()
113 expect(result.current.modalProps.visible).toBe(false)
114 })
115
116 it('uses the latest checkIsDirty and onClose callbacks', () => {
117 const onCloseA = vi.fn()
118 const onCloseB = vi.fn()
119 let isDirty = false
120
121 const { result, rerender } = renderHook(
122 ({ onClose }: { onClose: () => void }) =>
123 useConfirmOnClose({
124 checkIsDirty: () => isDirty,
125 onClose,
126 }),
127 {
128 initialProps: { onClose: onCloseA },
129 }
130 )
131
132 act(() => {
133 result.current.confirmOnClose()
134 })
135
136 expect(onCloseA).toHaveBeenCalledTimes(1)
137
138 isDirty = true
139 rerender({ onClose: onCloseB })
140
141 act(() => {
142 result.current.confirmOnClose()
143 })
144
145 expect(result.current.modalProps.visible).toBe(true)
146
147 act(() => {
148 result.current.modalProps.onClose()
149 })
150
151 expect(onCloseB).toHaveBeenCalledTimes(1)
152 })
153})