confirm-dialog.test.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import type { SubmitEvent } from 'react'
  2. import { describe, expect, it, vi } from 'vitest'
  3. import { render } from 'vitest-browser-react'
  4. import { userEvent } from 'vitest/browser'
  5. import { ConfirmDialog } from './confirm-dialog'
  6. describe('ConfirmDialog', () => {
  7. it('renders title, description, and default buttons', async () => {
  8. const { getByRole, getByText } = await render(
  9. <ConfirmDialog
  10. open
  11. onOpenChange={vi.fn()}
  12. title='Delete item'
  13. desc='This action cannot be undone.'
  14. handleConfirm={vi.fn()}
  15. />
  16. )
  17. await expect
  18. .element(getByRole('heading', { name: 'Delete item' }))
  19. .toBeInTheDocument()
  20. await expect
  21. .element(getByText('This action cannot be undone.'))
  22. .toBeInTheDocument()
  23. await expect
  24. .element(getByRole('button', { name: 'Cancel' }))
  25. .toBeInTheDocument()
  26. await expect
  27. .element(getByRole('button', { name: 'Continue' }))
  28. .toBeInTheDocument()
  29. })
  30. it('calls handleConfirm when the confirm button is clicked', async () => {
  31. const handleConfirm = vi.fn()
  32. const { getByRole } = await render(
  33. <ConfirmDialog
  34. open
  35. onOpenChange={vi.fn()}
  36. title='Sign out'
  37. desc='Are you sure?'
  38. confirmText='Sign out'
  39. handleConfirm={handleConfirm}
  40. />
  41. )
  42. await userEvent.click(getByRole('button', { name: 'Sign out' }))
  43. expect(handleConfirm).toHaveBeenCalledOnce()
  44. })
  45. it('disables confirm when disabled is true', async () => {
  46. const handleConfirm = vi.fn()
  47. const { getByRole } = await render(
  48. <ConfirmDialog
  49. open
  50. onOpenChange={vi.fn()}
  51. title='Danger'
  52. desc='...'
  53. disabled
  54. handleConfirm={handleConfirm}
  55. />
  56. )
  57. const confirm = getByRole('button', { name: 'Continue' })
  58. await expect.element(confirm).toBeDisabled()
  59. expect(handleConfirm).not.toHaveBeenCalled()
  60. })
  61. it('when isLoading is true, disables cancel and confirm', async () => {
  62. const handleConfirm = vi.fn()
  63. const { getByRole } = await render(
  64. <ConfirmDialog
  65. open
  66. onOpenChange={vi.fn()}
  67. title='Loading'
  68. desc='...'
  69. isLoading
  70. handleConfirm={handleConfirm}
  71. />
  72. )
  73. await expect.element(getByRole('button', { name: 'Cancel' })).toBeDisabled()
  74. await expect
  75. .element(getByRole('button', { name: 'Continue' }))
  76. .toBeDisabled()
  77. })
  78. it('supports custom button texts', async () => {
  79. const { getByRole } = await render(
  80. <ConfirmDialog
  81. open
  82. onOpenChange={vi.fn()}
  83. title='Delete'
  84. desc='...'
  85. cancelBtnText='No'
  86. confirmText='Yes'
  87. handleConfirm={vi.fn()}
  88. />
  89. )
  90. await expect
  91. .element(getByRole('button', { name: 'No' }))
  92. .toBeInTheDocument()
  93. await expect
  94. .element(getByRole('button', { name: 'Yes' }))
  95. .toBeInTheDocument()
  96. })
  97. it('renders confirm as submit button linked to desc form when `form` is set', async () => {
  98. const { getByRole } = await render(
  99. <ConfirmDialog
  100. open
  101. onOpenChange={vi.fn()}
  102. title='Delete tasks'
  103. form='tasks-multi-delete-form'
  104. desc={
  105. <form id='tasks-multi-delete-form' className='space-y-4'>
  106. <p>Type DELETE to confirm.</p>
  107. </form>
  108. }
  109. confirmText='Delete'
  110. destructive
  111. />
  112. )
  113. const deleteBtn = getByRole('button', { name: 'Delete' })
  114. await expect.element(deleteBtn).toHaveAttribute('type', 'submit')
  115. await expect
  116. .element(deleteBtn)
  117. .toHaveAttribute('form', 'tasks-multi-delete-form')
  118. })
  119. it('submits the desc form when confirm is clicked (form prop, no handleConfirm)', async () => {
  120. const handleFormSubmit = vi.fn((e: SubmitEvent<HTMLFormElement>) => {
  121. e.preventDefault()
  122. })
  123. const { getByRole } = await render(
  124. <ConfirmDialog
  125. open
  126. onOpenChange={vi.fn()}
  127. title='Delete'
  128. form='users-delete-form'
  129. desc={
  130. <form
  131. id='users-delete-form'
  132. onSubmit={handleFormSubmit}
  133. className='space-y-4'
  134. >
  135. <p>Confirm deletion.</p>
  136. </form>
  137. }
  138. confirmText='Delete'
  139. destructive
  140. />
  141. )
  142. await userEvent.click(getByRole('button', { name: 'Delete' }))
  143. expect(handleFormSubmit).toHaveBeenCalledOnce()
  144. })
  145. it('submits the form when Enter key is pressed', async () => {
  146. const handleFormSubmit = vi.fn((e: SubmitEvent<HTMLFormElement>) => {
  147. e.preventDefault()
  148. })
  149. const { getByPlaceholder } = await render(
  150. <ConfirmDialog
  151. open
  152. onOpenChange={vi.fn()}
  153. title='Delete'
  154. form='users-delete-form'
  155. desc={
  156. <form
  157. id='users-delete-form'
  158. onSubmit={handleFormSubmit}
  159. className='space-y-4'
  160. >
  161. <input type='text' name='username' placeholder='username' />
  162. </form>
  163. }
  164. confirmText='Delete'
  165. destructive
  166. />
  167. )
  168. await userEvent.fill(getByPlaceholder('username'), 'test')
  169. await userEvent.keyboard('{Enter}')
  170. expect(handleFormSubmit).toHaveBeenCalledOnce()
  171. })
  172. it('does not submit the form when confirm is disabled (typed confirmation mismatch)', async () => {
  173. const handleFormSubmit = vi.fn((e: SubmitEvent<HTMLFormElement>) => {
  174. e.preventDefault()
  175. })
  176. const { getByRole } = await render(
  177. <ConfirmDialog
  178. open
  179. onOpenChange={vi.fn()}
  180. title='Delete'
  181. form='users-delete-form'
  182. disabled
  183. desc={
  184. <form id='users-delete-form' onSubmit={handleFormSubmit}>
  185. <p>Enter username to enable Delete.</p>
  186. </form>
  187. }
  188. confirmText='Delete'
  189. destructive
  190. />
  191. )
  192. const deleteBtn = getByRole('button', { name: 'Delete' })
  193. await expect.element(deleteBtn).toBeDisabled()
  194. expect(handleFormSubmit).not.toHaveBeenCalled()
  195. })
  196. })