ticket-detail-view-time-accounting.spec.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { getNode } from '@formkit/core'
  3. import { within } from '@testing-library/vue'
  4. import { expect } from 'vitest'
  5. import { visitView } from '#tests/support/components/visitView.ts'
  6. import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
  7. import { mockPermissions } from '#tests/support/mock-permissions.ts'
  8. import { mockFormUpdaterQuery } from '#shared/components/Form/graphql/queries/formUpdater.mocks.ts'
  9. import {
  10. mockTicketUpdateMutation,
  11. waitForTicketUpdateMutationCalls,
  12. } from '#shared/entities/ticket/graphql/mutations/update.mocks.ts'
  13. import { mockTicketQuery } from '#shared/entities/ticket/graphql/queries/ticket.mocks.ts'
  14. import { createDummyTicket } from '#shared/entities/ticket-article/__tests__/mocks/ticket.ts'
  15. import { EnumUserErrorException } from '#shared/graphql/types.ts'
  16. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  17. import { mockLinkListQuery } from '../graphql/queries/linkList.mocks.ts'
  18. describe('Ticket detail view', () => {
  19. beforeEach(() => {
  20. mockPermissions(['ticket.agent'])
  21. mockLinkListQuery({
  22. linkList: [],
  23. })
  24. })
  25. describe('Time accounting', () => {
  26. it('shows accounted time information in sidebar', async () => {
  27. await mockApplicationConfig({
  28. time_accounting_types: true,
  29. })
  30. const ticket = createDummyTicket({
  31. state: {
  32. id: convertToGraphQLId('Ticket::State', 2),
  33. name: 'open',
  34. stateType: {
  35. id: convertToGraphQLId('TicketStateType', 2),
  36. name: 'open',
  37. },
  38. },
  39. articleType: 'email',
  40. defaultPolicy: {
  41. update: true,
  42. agentReadAccess: true,
  43. },
  44. timeUnit: 15,
  45. timeUnitsPerType: [
  46. {
  47. name: 'None',
  48. timeUnit: 6,
  49. },
  50. {
  51. name: 'Finance',
  52. timeUnit: 5,
  53. },
  54. {
  55. name: 'Business',
  56. timeUnit: 4,
  57. },
  58. ],
  59. })
  60. mockTicketQuery({
  61. ticket,
  62. })
  63. const view = await visitView('/tickets/1')
  64. const sidebar = view.getByLabelText('Content sidebar')
  65. expect(
  66. within(sidebar).getByRole('heading', {
  67. level: 3,
  68. name: 'Accounted Time',
  69. }),
  70. ).toBeInTheDocument()
  71. expect(within(sidebar).getByText('Total')).toBeInTheDocument()
  72. expect(within(sidebar).getByText('15')).toBeInTheDocument()
  73. })
  74. it('opens time accounting flyout when the condition is met', async () => {
  75. mockApplicationConfig({
  76. ui_ticket_zoom_article_note_new_internal: true,
  77. time_accounting: true,
  78. time_accounting_unit: '',
  79. time_accounting_types: false,
  80. })
  81. const ticket = createDummyTicket({
  82. state: {
  83. id: convertToGraphQLId('Ticket::State', 2),
  84. name: 'open',
  85. stateType: {
  86. id: convertToGraphQLId('TicketStateType', 2),
  87. name: 'open',
  88. },
  89. },
  90. articleType: 'email',
  91. defaultPolicy: {
  92. update: true,
  93. agentReadAccess: true,
  94. },
  95. })
  96. mockTicketQuery({
  97. ticket,
  98. })
  99. mockFormUpdaterQuery({
  100. formUpdater: {
  101. fields: {
  102. group_id: {
  103. options: [
  104. {
  105. value: 1,
  106. label: 'Users',
  107. },
  108. {
  109. value: 2,
  110. label: 'test group',
  111. },
  112. ],
  113. },
  114. owner_id: {
  115. options: [
  116. {
  117. value: 3,
  118. label: 'Test Admin Agent',
  119. },
  120. ],
  121. },
  122. state_id: {
  123. options: [
  124. {
  125. value: 4,
  126. label: 'closed',
  127. },
  128. {
  129. value: 2,
  130. label: 'open',
  131. },
  132. {
  133. value: 6,
  134. label: 'pending close',
  135. },
  136. {
  137. value: 3,
  138. label: 'pending reminder',
  139. },
  140. ],
  141. },
  142. pending_time: {
  143. show: false,
  144. },
  145. priority_id: {
  146. options: [
  147. {
  148. value: 1,
  149. label: '1 low',
  150. },
  151. {
  152. value: 2,
  153. label: '2 normal',
  154. },
  155. {
  156. value: 3,
  157. label: '3 high',
  158. },
  159. ],
  160. },
  161. },
  162. flags: {
  163. newArticlePresent: false,
  164. },
  165. },
  166. })
  167. const view = await visitView('/tickets/1')
  168. await view.events.click(
  169. await view.findByRole('button', { name: 'Add internal note' }),
  170. )
  171. await view.events.type(
  172. await view.findByRole('textbox', { name: 'Text' }),
  173. 'Foo note',
  174. )
  175. mockTicketUpdateMutation({
  176. ticketUpdate: {
  177. ticket: null,
  178. errors: [
  179. {
  180. message: 'The ticket time accounting condition is met.',
  181. exception:
  182. EnumUserErrorException.ServiceTicketUpdateValidatorTimeAccountingError,
  183. },
  184. ],
  185. },
  186. })
  187. mockFormUpdaterQuery({
  188. formUpdater: {
  189. fields: {},
  190. },
  191. })
  192. await view.events.click(
  193. await view.findByRole('button', { name: 'Update' }),
  194. )
  195. await waitForTicketUpdateMutationCalls()
  196. const flyout = await view.findByRole('complementary', {
  197. name: 'Time Accounting',
  198. })
  199. expect(
  200. within(flyout).getByRole('heading', {
  201. level: 2,
  202. }),
  203. ).toHaveTextContent('Time Accounting')
  204. await view.events.type(
  205. await within(flyout).findByLabelText('Accounted Time'),
  206. '1',
  207. )
  208. await getNode('form-ticket-time-accounting')?.settled
  209. mockTicketUpdateMutation({
  210. ticketUpdate: {
  211. ticket,
  212. errors: null,
  213. },
  214. })
  215. await view.events.click(
  216. within(flyout).getByRole('button', {
  217. name: 'Account Time',
  218. }),
  219. )
  220. const calls = await waitForTicketUpdateMutationCalls()
  221. expect(calls.at(-1)?.variables).toEqual(
  222. expect.objectContaining({
  223. input: expect.objectContaining({
  224. article: expect.objectContaining({
  225. timeUnit: 1,
  226. }),
  227. }),
  228. }),
  229. )
  230. expect(
  231. view.queryByRole('complementary', {
  232. name: 'Time Accounting',
  233. }),
  234. ).not.toBeInTheDocument()
  235. })
  236. })
  237. })