project-notifications-setting.test.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. commentOnOwnProject: true,
  11. commentOnInvitedProject: true,
  12. repliesOnAuthoredThread: true,
  13. repliesOnParticipatingThread: true,
  14. commentResolvedOnAuthoredThread: true,
  15. commentResolvedOnParticipatingThread: true,
  16. commentReopenedOnAuthoredThread: true,
  17. commentReopenedOnParticipatingThread: true,
  18. trackedChangesOnOwnProject: true,
  19. trackedChangesOnInvitedProject: true,
  20. trackChangesAcceptedOnAuthoredChange: true,
  21. }
  22. const repliesOnlyPreferences = {
  23. commentOnOwnProject: false,
  24. commentOnInvitedProject: false,
  25. repliesOnAuthoredThread: true,
  26. repliesOnParticipatingThread: true,
  27. commentResolvedOnAuthoredThread: true,
  28. commentResolvedOnParticipatingThread: true,
  29. commentReopenedOnAuthoredThread: true,
  30. commentReopenedOnParticipatingThread: true,
  31. trackedChangesOnOwnProject: false,
  32. trackedChangesOnInvitedProject: false,
  33. trackChangesAcceptedOnAuthoredChange: true,
  34. }
  35. const globallyMutedPreferences = {
  36. trackedChangesOnOwnProject: false,
  37. trackedChangesOnInvitedProject: false,
  38. commentOnOwnProject: false,
  39. commentOnInvitedProject: false,
  40. repliesOnAuthoredThread: false,
  41. repliesOnParticipatingThread: false,
  42. muteAllNotifications: true,
  43. commentResolvedOnAuthoredThread: false,
  44. commentResolvedOnParticipatingThread: false,
  45. commentReopenedOnAuthoredThread: false,
  46. commentReopenedOnParticipatingThread: false,
  47. trackChangesAcceptedOnAuthoredChange: false,
  48. }
  49. const allNotificationsOff = {
  50. commentOnOwnProject: false,
  51. commentOnInvitedProject: false,
  52. repliesOnAuthoredThread: false,
  53. repliesOnParticipatingThread: false,
  54. commentResolvedOnAuthoredThread: false,
  55. commentResolvedOnParticipatingThread: false,
  56. commentReopenedOnAuthoredThread: false,
  57. commentReopenedOnParticipatingThread: false,
  58. trackedChangesOnOwnProject: false,
  59. trackedChangesOnInvitedProject: false,
  60. trackChangesAcceptedOnAuthoredChange: false,
  61. }
  62. const defaultPreferences = {
  63. trackedChangesOnOwnProject: true,
  64. trackedChangesOnInvitedProject: false,
  65. commentOnOwnProject: true,
  66. commentOnInvitedProject: false,
  67. repliesOnAuthoredThread: true,
  68. repliesOnParticipatingThread: true,
  69. muteAllNotifications: false,
  70. commentResolvedOnAuthoredThread: true,
  71. commentResolvedOnParticipatingThread: true,
  72. commentReopenedOnAuthoredThread: true,
  73. commentReopenedOnParticipatingThread: true,
  74. trackChangesAcceptedOnAuthoredChange: true,
  75. }
  76. function renderComponent(props: { permissionsLevel?: string } = {}) {
  77. return render(
  78. <EditorProviders permissionsLevel={props.permissionsLevel as any}>
  79. <SettingsModalProvider>
  80. <ProjectNotificationsSetting />
  81. </SettingsModalProvider>
  82. </EditorProviders>
  83. )
  84. }
  85. describe('<ProjectNotificationsSetting />', function () {
  86. afterEach(function () {
  87. fetchMock.removeRoutes().clearHistory()
  88. })
  89. it('shows loading indicator while preferences are loading', async function () {
  90. fetchMock.get(preferencesUrl, new Promise(() => {}))
  91. renderComponent()
  92. expect(await screen.findByRole('status')).to.exist
  93. expect(screen.queryByLabelText('All project activity', { exact: false })).to
  94. .not.exist
  95. })
  96. it('selects "All project activity" when all notifications are on', async function () {
  97. fetchMock.get(preferencesUrl, allNotificationsOn)
  98. renderComponent()
  99. await waitFor(
  100. () =>
  101. expect(
  102. (
  103. screen.getByLabelText('All project activity', {
  104. exact: false,
  105. }) as HTMLInputElement
  106. ).checked
  107. ).to.be.true
  108. )
  109. expect(
  110. (
  111. screen.getByLabelText('Replies to your activity only', {
  112. exact: false,
  113. }) as HTMLInputElement
  114. ).checked
  115. ).to.be.false
  116. expect(
  117. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  118. .checked
  119. ).to.be.false
  120. })
  121. it('selects "Replies to your activity only" when only reply notifications are on', async function () {
  122. fetchMock.get(preferencesUrl, repliesOnlyPreferences)
  123. renderComponent()
  124. await waitFor(
  125. () =>
  126. expect(
  127. (
  128. screen.getByLabelText('Replies to your activity only', {
  129. exact: false,
  130. }) as HTMLInputElement
  131. ).checked
  132. ).to.be.true
  133. )
  134. expect(
  135. (
  136. screen.getByLabelText('All project activity', {
  137. exact: false,
  138. }) as HTMLInputElement
  139. ).checked
  140. ).to.be.false
  141. expect(
  142. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  143. .checked
  144. ).to.be.false
  145. })
  146. it('selects "Off" when all notifications are off', async function () {
  147. fetchMock.get(preferencesUrl, allNotificationsOff)
  148. renderComponent()
  149. await waitFor(
  150. () =>
  151. expect(
  152. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  153. .checked
  154. ).to.be.true
  155. )
  156. expect(
  157. (
  158. screen.getByLabelText('All project activity', {
  159. exact: false,
  160. }) as HTMLInputElement
  161. ).checked
  162. ).to.be.false
  163. expect(
  164. (
  165. screen.getByLabelText('Replies to your activity only', {
  166. exact: false,
  167. }) as HTMLInputElement
  168. ).checked
  169. ).to.be.false
  170. })
  171. it('shows muted message and hides radio buttons when muteAllNotifications is true', async function () {
  172. fetchMock.get(preferencesUrl, globallyMutedPreferences)
  173. renderComponent()
  174. await waitFor(
  175. () =>
  176. expect(
  177. screen.getByText(
  178. 'You are not receiving any notifications, as you have disabled all project notifications.',
  179. { exact: false }
  180. )
  181. ).to.exist
  182. )
  183. expect(
  184. screen.getByRole('link', { name: 'Change settings' }).getAttribute('href')
  185. ).to.equal('/user/notification-preferences')
  186. expect(screen.queryByLabelText('All project activity', { exact: false })).to
  187. .not.exist
  188. expect(
  189. screen.queryByLabelText('Replies to your activity only', { exact: false })
  190. ).to.not.exist
  191. expect(screen.queryByLabelText('Off', { exact: false })).to.not.exist
  192. })
  193. it('POSTs "replies" preferences when "Replies to your activity only" is selected', async function () {
  194. fetchMock.get(preferencesUrl, allNotificationsOn)
  195. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  196. renderComponent()
  197. await waitFor(
  198. () =>
  199. expect(
  200. (
  201. screen.getByLabelText('All project activity', {
  202. exact: false,
  203. }) as HTMLInputElement
  204. ).checked
  205. ).to.be.true
  206. )
  207. await userEvent.click(
  208. screen.getByLabelText('Replies to your activity only', { exact: false })
  209. )
  210. expect(
  211. saveMock.callHistory.called(preferencesUrl, {
  212. body: repliesOnlyPreferences,
  213. })
  214. ).to.be.true
  215. })
  216. it('POSTs "off" preferences when "Off" is selected', async function () {
  217. fetchMock.get(preferencesUrl, allNotificationsOn)
  218. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  219. renderComponent()
  220. await waitFor(
  221. () =>
  222. expect(
  223. (
  224. screen.getByLabelText('All project activity', {
  225. exact: false,
  226. }) as HTMLInputElement
  227. ).checked
  228. ).to.be.true
  229. )
  230. await userEvent.click(screen.getByLabelText('Off', { exact: false }))
  231. expect(
  232. saveMock.callHistory.called(preferencesUrl, {
  233. body: allNotificationsOff,
  234. })
  235. ).to.be.true
  236. })
  237. it('shows "all" for owner with default preferences', async function () {
  238. fetchMock.get(preferencesUrl, defaultPreferences)
  239. renderComponent({ permissionsLevel: 'owner' })
  240. await waitFor(
  241. () =>
  242. expect(
  243. (
  244. screen.getByLabelText('All project activity', {
  245. exact: false,
  246. }) as HTMLInputElement
  247. ).checked
  248. ).to.be.true
  249. )
  250. })
  251. it('shows "replies" for invitee with default preferences', async function () {
  252. fetchMock.get(preferencesUrl, defaultPreferences)
  253. renderComponent({ permissionsLevel: 'readAndWrite' })
  254. await waitFor(
  255. () =>
  256. expect(
  257. (
  258. screen.getByLabelText('Replies to your activity only', {
  259. exact: false,
  260. }) as HTMLInputElement
  261. ).checked
  262. ).to.be.true
  263. )
  264. })
  265. it('POSTs "all" preferences when "All project activity" is selected', async function () {
  266. fetchMock.get(preferencesUrl, allNotificationsOff)
  267. const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
  268. renderComponent()
  269. await waitFor(
  270. () =>
  271. expect(
  272. (screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
  273. .checked
  274. ).to.be.true
  275. )
  276. await userEvent.click(
  277. screen.getByLabelText('All project activity', { exact: false })
  278. )
  279. expect(
  280. saveMock.callHistory.called(preferencesUrl, {
  281. body: allNotificationsOn,
  282. })
  283. ).to.be.true
  284. })
  285. })