You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.spec.tsx 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import type { ReactNode } from 'react'
  2. import React from 'react'
  3. import { act, render, screen, waitFor } from '@testing-library/react'
  4. import Toast, { ToastProvider, useToastContext } from '.'
  5. import '@testing-library/jest-dom'
  6. import { noop } from 'lodash-es'
  7. // Mock timers for testing timeouts
  8. jest.useFakeTimers()
  9. const TestComponent = () => {
  10. const { notify, close } = useToastContext()
  11. return (
  12. <div>
  13. <button onClick={() => notify({ message: 'Notification message', type: 'info' })}>
  14. Show Toast
  15. </button>
  16. <button onClick={close}>Close Toast</button>
  17. </div>
  18. )
  19. }
  20. describe('Toast', () => {
  21. describe('Toast Component', () => {
  22. test('renders toast with correct type and message', () => {
  23. render(
  24. <ToastProvider>
  25. <Toast type="success" message="Success message" />
  26. </ToastProvider>,
  27. )
  28. expect(screen.getByText('Success message')).toBeInTheDocument()
  29. })
  30. test('renders with different types', () => {
  31. const { rerender } = render(
  32. <ToastProvider>
  33. <Toast type="success" message="Success message" />
  34. </ToastProvider>,
  35. )
  36. expect(document.querySelector('.text-text-success')).toBeInTheDocument()
  37. rerender(
  38. <ToastProvider>
  39. <Toast type="error" message="Error message" />
  40. </ToastProvider>,
  41. )
  42. expect(document.querySelector('.text-text-destructive')).toBeInTheDocument()
  43. })
  44. test('renders with custom component', () => {
  45. render(
  46. <ToastProvider>
  47. <Toast
  48. message="Message with custom component"
  49. customComponent={<span data-testid="custom-component">Custom</span>}
  50. />
  51. </ToastProvider>,
  52. )
  53. expect(screen.getByTestId('custom-component')).toBeInTheDocument()
  54. })
  55. test('renders children content', () => {
  56. render(
  57. <ToastProvider>
  58. <Toast message="Message with children">
  59. <span>Additional information</span>
  60. </Toast>
  61. </ToastProvider>,
  62. )
  63. expect(screen.getByText('Additional information')).toBeInTheDocument()
  64. })
  65. test('does not render close button when close is undefined', () => {
  66. // Create a modified context where close is undefined
  67. const CustomToastContext = React.createContext({ notify: noop, close: undefined })
  68. // Create a wrapper component using the custom context
  69. const Wrapper = ({ children }: { children: ReactNode }) => (
  70. <CustomToastContext.Provider value={{ notify: noop, close: undefined }}>
  71. {children}
  72. </CustomToastContext.Provider>
  73. )
  74. render(
  75. <Wrapper>
  76. <Toast message="No close button" type="info" />
  77. </Wrapper>,
  78. )
  79. expect(screen.getByText('No close button')).toBeInTheDocument()
  80. // Ensure the close button is not rendered
  81. expect(document.querySelector('.h-4.w-4.shrink-0.text-text-tertiary')).not.toBeInTheDocument()
  82. })
  83. })
  84. describe('ToastProvider and Context', () => {
  85. test('shows and hides toast using context', async () => {
  86. render(
  87. <ToastProvider>
  88. <TestComponent />
  89. </ToastProvider>,
  90. )
  91. // No toast initially
  92. expect(screen.queryByText('Notification message')).not.toBeInTheDocument()
  93. // Show toast
  94. act(() => {
  95. screen.getByText('Show Toast').click()
  96. })
  97. expect(screen.getByText('Notification message')).toBeInTheDocument()
  98. // Close toast
  99. act(() => {
  100. screen.getByText('Close Toast').click()
  101. })
  102. expect(screen.queryByText('Notification message')).not.toBeInTheDocument()
  103. })
  104. test('automatically hides toast after duration', async () => {
  105. render(
  106. <ToastProvider>
  107. <TestComponent />
  108. </ToastProvider>,
  109. )
  110. // Show toast
  111. act(() => {
  112. screen.getByText('Show Toast').click()
  113. })
  114. expect(screen.getByText('Notification message')).toBeInTheDocument()
  115. // Fast-forward timer
  116. act(() => {
  117. jest.advanceTimersByTime(3000) // Default for info type is 3000ms
  118. })
  119. // Toast should be gone
  120. await waitFor(() => {
  121. expect(screen.queryByText('Notification message')).not.toBeInTheDocument()
  122. })
  123. })
  124. })
  125. describe('Toast.notify static method', () => {
  126. test('creates and removes toast from DOM', async () => {
  127. act(() => {
  128. // Call the static method
  129. Toast.notify({ message: 'Static notification', type: 'warning' })
  130. })
  131. // Toast should be in document
  132. expect(screen.getByText('Static notification')).toBeInTheDocument()
  133. // Fast-forward timer
  134. act(() => {
  135. jest.advanceTimersByTime(6000) // Default for warning type is 6000ms
  136. })
  137. // Toast should be removed
  138. await waitFor(() => {
  139. expect(screen.queryByText('Static notification')).not.toBeInTheDocument()
  140. })
  141. })
  142. test('calls onClose callback after duration', async () => {
  143. const onCloseMock = jest.fn()
  144. act(() => {
  145. Toast.notify({
  146. message: 'Closing notification',
  147. type: 'success',
  148. onClose: onCloseMock,
  149. })
  150. })
  151. // Fast-forward timer
  152. act(() => {
  153. jest.advanceTimersByTime(3000) // Default for success type is 3000ms
  154. })
  155. // onClose should be called
  156. await waitFor(() => {
  157. expect(onCloseMock).toHaveBeenCalled()
  158. })
  159. })
  160. })
  161. })