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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. // Copyright (C) 2012-2025 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. 'YYYY-MM-DD - YYYY-MM-DD',
  224. )
  225. expect(view.getByLabelText('Replacement agent')).toHaveValue('')
  226. expect(view.getByLabelText('Active')).not.toBeChecked()
  227. })
  228. it('can set date range', async () => {
  229. const view = await visitView('/personal-setting/out-of-office')
  230. const input = view.getByLabelText('Start and end date')
  231. await view.events.type(input, '2024-01-02 - 2024-02-02')
  232. await view.events.keyboard('{Enter}')
  233. await view.events.click(view.getByText('Save Out of Office'))
  234. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  235. expect(calls.at(-1)?.variables).toEqual(
  236. expect.objectContaining({
  237. input: expect.objectContaining({
  238. startAt: '2024-01-02',
  239. endAt: '2024-02-02',
  240. }),
  241. }),
  242. )
  243. })
  244. it('can set replacement agent', async () => {
  245. const view = await visitView('/personal-setting/out-of-office')
  246. const inputAgent = view.getByLabelText('Replacement agent')
  247. await view.events.click(inputAgent)
  248. const filterElement = getByRole(inputAgent, 'searchbox')
  249. mockAutocompleteSearchAgentQuery({
  250. autocompleteSearchAgent: [agentAutocompleteOptions[0]],
  251. })
  252. await view.events.type(filterElement, agentAutocompleteOptions[0].label)
  253. await waitForAutocompleteSearchAgentQueryCalls()
  254. await view.events.click(view.getAllByRole('option')[0])
  255. await view.events.click(view.getByText('Save Out of Office'))
  256. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  257. expect(calls.at(-1)?.variables).toEqual(
  258. expect.objectContaining({
  259. input: expect.objectContaining({
  260. replacementId: convertToGraphQLId(
  261. 'User',
  262. agentAutocompleteOptions[0].value,
  263. ),
  264. }),
  265. }),
  266. )
  267. })
  268. it('can enable Out of Office with settings', async () => {
  269. const view = await visitView('/personal-setting/out-of-office')
  270. const inputLabel = view.getByLabelText('Active')
  271. await view.events.click(inputLabel)
  272. const inputDate = view.getByLabelText('Start and end date')
  273. await view.events.type(inputDate, '2024-01-02 - 2024-02-02')
  274. await view.events.keyboard('{Enter}')
  275. const inputAgent = view.getByLabelText('Replacement agent')
  276. await view.events.click(inputAgent)
  277. const filterElement = getByRole(inputAgent, 'searchbox')
  278. mockAutocompleteSearchAgentQuery({
  279. autocompleteSearchAgent: [agentAutocompleteOptions[0]],
  280. })
  281. await view.events.type(filterElement, agentAutocompleteOptions[0].label)
  282. await waitForAutocompleteSearchAgentQueryCalls()
  283. await view.events.click(view.getAllByRole('option')[0])
  284. await view.events.click(view.getByText('Save Out of Office'))
  285. const calls = await waitForUserCurrentOutOfOfficeMutationCalls()
  286. expect(calls.at(-1)?.variables).toEqual(
  287. expect.objectContaining({
  288. input: expect.objectContaining({
  289. startAt: '2024-01-02',
  290. endAt: '2024-02-02',
  291. replacementId: convertToGraphQLId(
  292. 'User',
  293. agentAutocompleteOptions[0].value,
  294. ),
  295. enabled: true,
  296. }),
  297. }),
  298. )
  299. })
  300. it('cannot enable Out of Office without date range and replacement agent', async () => {
  301. const view = await visitView('/personal-setting/out-of-office')
  302. const input = view.getByLabelText('Active')
  303. await view.events.click(input)
  304. await view.events.click(view.getByText('Save Out of Office'))
  305. const inputDate = view.getByLabelText('Start and end date')
  306. expect(inputDate).toBeDescribedBy('This field is required.')
  307. })
  308. })
  309. })