project-notifications-setting.test.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import { screen, render, waitFor } from '@testing-library/react'
  2. import { expect } from 'chai'
  3. import fetchMock from 'fetch-mock'
  4. import userEvent from '@testing-library/user-event'
  5. import { EditorProviders, PROJECT_ID } from '../../../helpers/editor-providers'
  6. import { SettingsModalProvider } from '@/features/settings/context/settings-modal-context'
  7. import ProjectNotificationsSetting from '@/features/settings/components/editor-settings/project-notifications-setting'
  8. const preferencesUrl = `/notifications/preferences/project/${PROJECT_ID}`
  9. const allNotificationsOn = {
  10. trackedChangesOnOwnProject: true,
  11. trackedChangesOnInvitedProject: true,
  12. commentOnOwnProject: true,
  13. commentOnInvitedProject: true,
  14. repliesOnOwnProject: true,
  15. repliesOnInvitedProject: true,
  16. repliesOnAuthoredThread: true,
  17. repliesOnParticipatingThread: true,
  18. }
  19. const repliesOnlyPreferences = {
  20. trackedChangesOnOwnProject: false,
  21. trackedChangesOnInvitedProject: false,
  22. commentOnOwnProject: false,
  23. commentOnInvitedProject: false,
  24. repliesOnOwnProject: false,
  25. repliesOnInvitedProject: false,
  26. repliesOnAuthoredThread: true,
  27. repliesOnParticipatingThread: true,
  28. }
  29. const globallyMutedPreferences = {
  30. trackedChangesOnOwnProject: false,
  31. trackedChangesOnInvitedProject: false,
  32. commentOnOwnProject: false,
  33. commentOnInvitedProject: false,
  34. repliesOnOwnProject: false,
  35. repliesOnInvitedProject: false,
  36. repliesOnAuthoredThread: false,
  37. repliesOnParticipatingThread: false,
  38. muteAllNotifications: true,
  39. }
  40. const allNotificationsOff = {
  41. trackedChangesOnOwnProject: false,
  42. trackedChangesOnInvitedProject: false,
  43. commentOnOwnProject: false,
  44. commentOnInvitedProject: false,
  45. repliesOnOwnProject: false,
  46. repliesOnInvitedProject: false,
  47. repliesOnAuthoredThread: false,
  48. repliesOnParticipatingThread: false,
  49. }
  50. function renderComponent() {
  51. return render(
  52. <EditorProviders>
  53. <SettingsModalProvider>
  54. <ProjectNotificationsSetting />
  55. </SettingsModalProvider>
  56. </EditorProviders>
  57. )
  58. }
  59. describe('<ProjectNotificationsSetting />', function () {
  60. afterEach(function () {
  61. fetchMock.removeRoutes().clearHistory()
  62. })
  63. it('shows loading indicator while preferences are loading', async function () {
  64. fetchMock.get(preferencesUrl, new Promise(() => {}))
  65. renderComponent()
  66. expect(await screen.findByRole('status')).to.exist
  67. expect(screen.queryByLabelText('All project activity', { exact: false })).to
  68. .not.exist
  69. })
  70. it('selects "All project activity" when all notifications are on', async function () {
  71. fetchMock.get(preferencesUrl, allNotificationsOn)
  72. renderComponent()
  73. await waitFor(
  74. () =>
  75. expect(
  76. (
  77. screen.getByLabelText('All project activity', {
  78. exact: false,
  79. }) as HTMLInputElement
  80. ).checked
  81. ).to.be.true
  82. )
  83. expect(
  84. (
  85. screen.getByLabelText('Replies to your activity only', {
  86. exact: false,
  87. }) as HTMLInputElement
  88. ).checked
  89. ).to.be.false
  90. expect(
  91. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  92. .checked
  93. ).to.be.false
  94. })
  95. it('selects "Replies to your activity only" when only reply notifications are on', async function () {
  96. fetchMock.get(preferencesUrl, repliesOnlyPreferences)
  97. renderComponent()
  98. await waitFor(
  99. () =>
  100. expect(
  101. (
  102. screen.getByLabelText('Replies to your activity only', {
  103. exact: false,
  104. }) as HTMLInputElement
  105. ).checked
  106. ).to.be.true
  107. )
  108. expect(
  109. (
  110. screen.getByLabelText('All project activity', {
  111. exact: false,
  112. }) as HTMLInputElement
  113. ).checked
  114. ).to.be.false
  115. expect(
  116. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  117. .checked
  118. ).to.be.false
  119. })
  120. it('selects "Off" when all notifications are off', async function () {
  121. fetchMock.get(preferencesUrl, allNotificationsOff)
  122. renderComponent()
  123. await waitFor(
  124. () =>
  125. expect(
  126. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  127. .checked
  128. ).to.be.true
  129. )
  130. expect(
  131. (
  132. screen.getByLabelText('All project activity', {
  133. exact: false,
  134. }) as HTMLInputElement
  135. ).checked
  136. ).to.be.false
  137. expect(
  138. (
  139. screen.getByLabelText('Replies to your activity only', {
  140. exact: false,
  141. }) as HTMLInputElement
  142. ).checked
  143. ).to.be.false
  144. })
  145. it('shows muted message and hides radio buttons when muteAllNotifications is true', async function () {
  146. fetchMock.get(preferencesUrl, globallyMutedPreferences)
  147. renderComponent()
  148. await waitFor(
  149. () =>
  150. expect(
  151. screen.getByText(
  152. 'You are not receiving any notifications, as you have disabled all project notifications.',
  153. { exact: false }
  154. )
  155. ).to.exist
  156. )
  157. expect(
  158. screen.getByRole('link', { name: 'Change settings' }).getAttribute('href')
  159. ).to.equal('/user/notification-preferences')
  160. expect(screen.queryByLabelText('All project activity', { exact: false })).to
  161. .not.exist
  162. expect(
  163. screen.queryByLabelText('Replies to your activity only', { exact: false })
  164. ).to.not.exist
  165. expect(screen.queryByLabelText('Off', { exact: false })).to.not.exist
  166. })
  167. it('POSTs "replies" preferences when "Replies to your activity only" is selected', async function () {
  168. fetchMock.get(preferencesUrl, allNotificationsOn)
  169. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  170. renderComponent()
  171. await waitFor(
  172. () =>
  173. expect(
  174. (
  175. screen.getByLabelText('All project activity', {
  176. exact: false,
  177. }) as HTMLInputElement
  178. ).checked
  179. ).to.be.true
  180. )
  181. await userEvent.click(
  182. screen.getByLabelText('Replies to your activity only', { exact: false })
  183. )
  184. expect(
  185. saveMock.callHistory.called(preferencesUrl, {
  186. body: repliesOnlyPreferences,
  187. })
  188. ).to.be.true
  189. })
  190. it('POSTs "off" preferences when "Off" is selected', async function () {
  191. fetchMock.get(preferencesUrl, allNotificationsOn)
  192. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  193. renderComponent()
  194. await waitFor(
  195. () =>
  196. expect(
  197. (
  198. screen.getByLabelText('All project activity', {
  199. exact: false,
  200. }) as HTMLInputElement
  201. ).checked
  202. ).to.be.true
  203. )
  204. await userEvent.click(screen.getByLabelText('Off', { exact: false }))
  205. expect(
  206. saveMock.callHistory.called(preferencesUrl, {
  207. body: allNotificationsOff,
  208. })
  209. ).to.be.true
  210. })
  211. it('POSTs "all" preferences when "All project activity" is selected', async function () {
  212. fetchMock.get(preferencesUrl, allNotificationsOff)
  213. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  214. renderComponent()
  215. await waitFor(
  216. () =>
  217. expect(
  218. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  219. .checked
  220. ).to.be.true
  221. )
  222. await userEvent.click(
  223. screen.getByLabelText('All project activity', { exact: false })
  224. )
  225. expect(
  226. saveMock.callHistory.called(preferencesUrl, {
  227. body: allNotificationsOn,
  228. })
  229. ).to.be.true
  230. })
  231. })