Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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