personal-setting-out-of-office.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { getByRole } from '@testing-library/vue'
  3. import { visitView } from '#tests/support/components/visitView.ts'
  4. import { mockUserCurrent } from '#tests/support/mock-userCurrent.ts'
  5. import { nullableMock, waitForNextTick } from '#tests/support/utils.ts'
  6. import {
  7. mockAutocompleteSearchAgentQuery,
  8. waitForAutocompleteSearchAgentQueryCalls,
  9. } from '#shared/components/Form/fields/FieldAgent/graphql/queries/autocompleteSearch/agent.mocks.ts'
  10. import type { AutocompleteSearchUserEntry } from '#shared/graphql/types.ts'
  11. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  12. import { waitForUserCurrentOutOfOfficeMutationCalls } from '../graphql/mutations/userCurrentOutOfOffice.mocks.ts'
  13. const agentAutocompleteOptions = [
  14. {
  15. __typename: 'AutocompleteSearchUserEntry',
  16. value: 1,
  17. label: 'foo',
  18. disabled: false,
  19. user: nullableMock({
  20. id: convertToGraphQLId('User', 1),
  21. internalId: 1,
  22. fullname: 'sample 1',
  23. }),
  24. },
  25. {
  26. __typename: 'AutocompleteSearchUserEntry',
  27. value: 2,
  28. label: 'bar',
  29. disabled: false,
  30. user: nullableMock({
  31. id: convertToGraphQLId('User', 2),
  32. internalId: 2,
  33. fullname: 'sample 1',
  34. }),
  35. },
  36. ] as AutocompleteSearchUserEntry[]
  37. describe('Out of Office page', () => {
  38. describe('when enabled', () => {
  39. beforeEach(() => {
  40. mockUserCurrent({
  41. id: '123',
  42. internalId: 1,
  43. firstname: 'John',
  44. lastname: 'Doe',
  45. outOfOffice: true,
  46. preferences: { out_of_office_text: 'OOF holiday' },
  47. outOfOfficeStartAt: '2024-03-01',
  48. outOfOfficeEndAt: '2024-04-01',
  49. outOfOfficeReplacement: {
  50. id: convertToGraphQLId('User', 256),
  51. internalId: 256,
  52. fullname: 'Example Agent',
  53. },
  54. })
  55. })
  56. it('loads current Out of Office settings', async () => {
  57. const view = await visitView('/personal-setting/out-of-office')
  58. expect(view.getByLabelText('Reason for absence')).toHaveValue(
  59. 'OOF holiday',
  60. )
  61. expect(view.getByLabelText('Start and end date')).toHaveValue(
  62. '2024-03-01 - 2024-04-01',
  63. )
  64. expect(view.getByLabelText('Replacement agent')).toHaveValue(
  65. 'Example Agent',
  66. )
  67. expect(view.getByLabelText('Active')).toBeChecked()
  68. })
  69. it('loads data updated elsewhere', async () => {
  70. const view = await visitView('/personal-setting/out-of-office')
  71. expect(view.getByLabelText('Reason for absence')).toHaveValue(
  72. 'OOF holiday',
  73. )
  74. mockUserCurrent({
  75. firstname: 'John',
  76. lastname: 'Doe',
  77. outOfOffice: true,
  78. preferences: { out_of_office_text: '' },
  79. outOfOfficeStartAt: '2024-03-01',
  80. outOfOfficeEndAt: '2024-04-01',
  81. outOfOfficeReplacement: {
  82. id: convertToGraphQLId('User', 256),
  83. internalId: 256,
  84. fullname: 'Example Agent',
  85. },
  86. })
  87. await waitForNextTick()
  88. expect(view.getByLabelText('Reason for absence')).toHaveValue('')
  89. })
  90. it('does not reset form if unrelated data was updated', async () => {
  91. const view = await visitView('/personal-setting/out-of-office')
  92. expect(view.getByLabelText('Reason for absence')).toHaveValue(
  93. 'OOF holiday',
  94. )
  95. const input = view.getByLabelText('Reason for absence')
  96. await view.events.clear(input)
  97. await view.events.type(input, 'new label')
  98. mockUserCurrent({
  99. firstname: 'John II',
  100. lastname: 'Doe',
  101. outOfOffice: true,
  102. preferences: { out_of_office_text: 'OOF holiday' },
  103. outOfOfficeStartAt: '2024-03-01',
  104. outOfOfficeEndAt: '2024-04-01',
  105. outOfOfficeReplacement: {
  106. id: convertToGraphQLId('User', 256),
  107. internalId: 256,
  108. fullname: 'Example Agent',
  109. },
  110. })
  111. await waitForNextTick()
  112. expect(view.getByLabelText('Reason for absence')).toHaveValue('new label')
  113. })
  114. it('shows success notification', async () => {
  115. const view = await visitView('/personal-setting/out-of-office')
  116. await view.events.click(view.getByText('Save Out of Office'))
  117. expect(
  118. view.getByText('Out of Office settings have been saved successfully'),
  119. ).toBeInTheDocument()
  120. })
  121. it('can clear label', async () => {
  122. const view = await visitView('/personal-setting/out-of-office')
  123. const input = view.getByLabelText('Reason for absence')
  124. await view.events.clear(input)
  125. await view.events.click(view.getByText('Save Out of Office'))
  126. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  127. expect(calls.at(-1)?.variables).toEqual(
  128. expect.objectContaining({
  129. input: expect.objectContaining({
  130. text: '',
  131. }),
  132. }),
  133. )
  134. })
  135. it('cannot set date range to blank', async () => {
  136. const view = await visitView('/personal-setting/out-of-office')
  137. const input = view.getByLabelText('Start and end date')
  138. const button = getByRole(input.parentElement!, 'button')
  139. await view.events.click(button)
  140. await view.events.click(view.getByText('Save Out of Office'))
  141. expect(input).toBeDescribedBy('This field is required.')
  142. })
  143. it('cannot set replacement agent to blank', async () => {
  144. const view = await visitView('/personal-setting/out-of-office')
  145. const input = view.getByLabelText('Replacement agent')
  146. const button = getByRole(input, 'button')
  147. await view.events.click(button)
  148. await view.events.click(view.getByText('Save Out of Office'))
  149. expect(input).toBeDescribedBy('This field is required.')
  150. })
  151. it('cannot set replacement agent to themselves', async () => {
  152. const view = await visitView('/personal-setting/out-of-office')
  153. const inputAgent = view.getByLabelText('Replacement agent')
  154. await view.events.click(inputAgent)
  155. const filterElement = getByRole(inputAgent, 'searchbox')
  156. mockAutocompleteSearchAgentQuery({
  157. autocompleteSearchAgent: agentAutocompleteOptions,
  158. })
  159. await view.events.type(filterElement, '*')
  160. const calls = await waitForAutocompleteSearchAgentQueryCalls()
  161. expect(calls.at(-1)?.variables).toEqual({
  162. input: expect.objectContaining({
  163. exceptInternalId: 1,
  164. }),
  165. })
  166. })
  167. it('can disable Out of Office', async () => {
  168. const view = await visitView('/personal-setting/out-of-office')
  169. const input = view.getByLabelText('Active')
  170. await view.events.click(input)
  171. await view.events.click(view.getByText('Save Out of Office'))
  172. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  173. expect(calls.at(-1)?.variables).toEqual(
  174. expect.objectContaining({
  175. input: expect.objectContaining({
  176. enabled: false,
  177. }),
  178. }),
  179. )
  180. })
  181. it('can disable Out of Office and unset settings', async () => {
  182. const view = await visitView('/personal-setting/out-of-office')
  183. const inputActivated = view.getByLabelText('Active')
  184. await view.events.click(inputActivated)
  185. const inputDate = view.getByLabelText('Start and end date')
  186. const buttonDate = getByRole(inputDate.parentElement!, 'button')
  187. await view.events.click(buttonDate)
  188. const inputLabel = view.getByLabelText('Reason for absence')
  189. await view.events.clear(inputLabel)
  190. const inputAgent = view.getByLabelText('Replacement agent')
  191. const buttonAgent = getByRole(inputAgent, 'button')
  192. await view.events.click(buttonAgent)
  193. await view.events.click(view.getByText('Save Out of Office'))
  194. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  195. expect(calls.at(-1)?.variables).toEqual(
  196. expect.objectContaining({
  197. input: expect.objectContaining({
  198. enabled: false,
  199. text: '',
  200. startAt: undefined,
  201. endAt: undefined,
  202. replacementId: undefined,
  203. }),
  204. }),
  205. )
  206. })
  207. })
  208. describe('when disabled', () => {
  209. beforeEach(() => {
  210. mockUserCurrent({
  211. firstname: 'John',
  212. lastname: 'Doe',
  213. outOfOffice: false,
  214. outOfOfficeStartAt: '',
  215. outOfOfficeEndAt: '',
  216. outOfOfficeReplacement: null,
  217. })
  218. })
  219. it('loads current Out of Office settings', async () => {
  220. const view = await visitView('/personal-setting/out-of-office')
  221. expect(view.getByLabelText('Reason for absence')).toHaveValue('')
  222. expect(view.getByLabelText('Start and end date')).toHaveValue('')
  223. expect(view.getByLabelText('Replacement agent')).toHaveValue('')
  224. expect(view.getByLabelText('Active')).not.toBeChecked()
  225. })
  226. it('can set date range', async () => {
  227. const view = await visitView('/personal-setting/out-of-office')
  228. const input = view.getByLabelText('Start and end date')
  229. await view.events.type(input, '2024-01-02 - 2024-02-02')
  230. await view.events.keyboard('{Enter}')
  231. await view.events.click(view.getByText('Save Out of Office'))
  232. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  233. expect(calls.at(-1)?.variables).toEqual(
  234. expect.objectContaining({
  235. input: expect.objectContaining({
  236. startAt: '2024-01-02',
  237. endAt: '2024-02-02',
  238. }),
  239. }),
  240. )
  241. })
  242. it('can set replacement agent', async () => {
  243. const view = await visitView('/personal-setting/out-of-office')
  244. const inputAgent = view.getByLabelText('Replacement agent')
  245. await view.events.click(inputAgent)
  246. const filterElement = getByRole(inputAgent, 'searchbox')
  247. mockAutocompleteSearchAgentQuery({
  248. autocompleteSearchAgent: [agentAutocompleteOptions[0]],
  249. })
  250. await view.events.type(filterElement, agentAutocompleteOptions[0].label)
  251. await waitForAutocompleteSearchAgentQueryCalls()
  252. await view.events.click(view.getAllByRole('option')[0])
  253. await view.events.click(view.getByText('Save Out of Office'))
  254. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  255. expect(calls.at(-1)?.variables).toEqual(
  256. expect.objectContaining({
  257. input: expect.objectContaining({
  258. replacementId: convertToGraphQLId(
  259. 'User',
  260. agentAutocompleteOptions[0].value,
  261. ),
  262. }),
  263. }),
  264. )
  265. })
  266. it('can enable Out of Office with settings', async () => {
  267. const view = await visitView('/personal-setting/out-of-office')
  268. const inputLabel = view.getByLabelText('Active')
  269. await view.events.click(inputLabel)
  270. const inputDate = view.getByLabelText('Start and end date')
  271. await view.events.type(inputDate, '2024-01-02 - 2024-02-02')
  272. await view.events.keyboard('{Enter}')
  273. const inputAgent = view.getByLabelText('Replacement agent')
  274. await view.events.click(inputAgent)
  275. const filterElement = getByRole(inputAgent, 'searchbox')
  276. mockAutocompleteSearchAgentQuery({
  277. autocompleteSearchAgent: [agentAutocompleteOptions[0]],
  278. })
  279. await view.events.type(filterElement, agentAutocompleteOptions[0].label)
  280. await waitForAutocompleteSearchAgentQueryCalls()
  281. await view.events.click(view.getAllByRole('option')[0])
  282. await view.events.click(view.getByText('Save Out of Office'))
  283. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  284. expect(calls.at(-1)?.variables).toEqual(
  285. expect.objectContaining({
  286. input: expect.objectContaining({
  287. startAt: '2024-01-02',
  288. endAt: '2024-02-02',
  289. replacementId: convertToGraphQLId(
  290. 'User',
  291. agentAutocompleteOptions[0].value,
  292. ),
  293. enabled: true,
  294. }),
  295. }),
  296. )
  297. })
  298. it('cannot enable Out of Office without date range and replacement agent', async () => {
  299. const view = await visitView('/personal-setting/out-of-office')
  300. const input = view.getByLabelText('Active')
  301. await view.events.click(input)
  302. await view.events.click(view.getByText('Save Out of Office'))
  303. const inputDate = view.getByLabelText('Start and end date')
  304. expect(inputDate).toBeDescribedBy('This field is required.')
  305. })
  306. })
  307. })