ticket-detail-view.spec.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { within } from '@testing-library/vue'
  3. import createArticle from '#tests/graphql/factories/TicketArticle.ts'
  4. import { getTestRouter } from '#tests/support/components/renderComponent.ts'
  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 { mockTicketArticlesQuery } from '#shared/entities/ticket/graphql/queries/ticket/articles.mocks.ts'
  9. import { mockTicketQuery } from '#shared/entities/ticket/graphql/queries/ticket.mocks.ts'
  10. import { createDummyArticle } from '#shared/entities/ticket-article/__tests__/mocks/ticket-articles.ts'
  11. import { createDummyTicket } from '#shared/entities/ticket-article/__tests__/mocks/ticket.ts'
  12. import type { TicketArticleEdge } from '#shared/graphql/types.ts'
  13. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  14. import { mockTicketChecklistQuery } from '#desktop/pages/ticket/graphql/queries/ticketChecklist.mocks.ts'
  15. import { getTicketChecklistUpdatesSubscriptionHandler } from '#desktop/pages/ticket/graphql/subscriptions/ticketChecklistUpdates.mocks.ts'
  16. describe('ticket detail view', () => {
  17. describe('errors', () => {
  18. it.todo('redirects if ticket id is not found', async () => {
  19. mockPermissions(['ticket.agent'])
  20. // :TODO test test as soon as the bug for the Query has complexity of 19726, has been resolved
  21. mockTicketQuery({
  22. ticket: null,
  23. })
  24. await visitView('/tickets/232')
  25. const router = getTestRouter()
  26. expect(router.currentRoute.value.name).toEqual('Error')
  27. })
  28. })
  29. it('shows see more button', async () => {
  30. mockPermissions(['ticket.agent'])
  31. mockTicketQuery({
  32. ticket: createDummyTicket(),
  33. })
  34. const firstArticleEdges: TicketArticleEdge[] = []
  35. const articlesEdges: TicketArticleEdge[] = []
  36. let count = 27
  37. while (count > 0) {
  38. const first = createArticle()
  39. const article = createArticle()
  40. if (count <= 5)
  41. firstArticleEdges.push(<TicketArticleEdge>{
  42. cursor: Buffer.from(count.toString()).toString('base64'),
  43. node: { ...first, internalId: count, sender: { name: 'Agent' } },
  44. })
  45. if (count > 5)
  46. articlesEdges.push(<TicketArticleEdge>{
  47. cursor: Buffer.from(count.toString()).toString('base64'),
  48. node: {
  49. ...article,
  50. internalId: 50 - count,
  51. sender: { name: 'Customer' },
  52. },
  53. })
  54. // eslint-disable-next-line no-plusplus
  55. count--
  56. }
  57. mockTicketArticlesQuery({
  58. articles: {
  59. totalCount: 50,
  60. edges: articlesEdges,
  61. pageInfo: {
  62. hasPreviousPage: articlesEdges.length > 0,
  63. startCursor:
  64. articlesEdges.length > 0 ? articlesEdges[0].cursor : null,
  65. endCursor: btoa('50'),
  66. },
  67. },
  68. firstArticles: {
  69. edges: firstArticleEdges,
  70. },
  71. })
  72. const view = await visitView('/tickets/1')
  73. const feed = view.getByRole('feed')
  74. const articles = within(feed).getAllByRole('article')
  75. expect(articles).toHaveLength(26) // 20 articles from end && 5 articles from the beginning 1 more button
  76. expect(
  77. within(articles.at(6) as HTMLElement).getByRole('button', {
  78. name: 'See more',
  79. }),
  80. ).toBeInTheDocument()
  81. })
  82. beforeEach(() => {
  83. mockPermissions(['ticket.agent'])
  84. mockTicketQuery({
  85. ticket: createDummyTicket(),
  86. })
  87. const testArticle = createDummyArticle({
  88. bodyWithUrls: 'foobar',
  89. })
  90. mockTicketArticlesQuery({
  91. articles: {
  92. totalCount: 1,
  93. edges: [{ node: testArticle }],
  94. },
  95. firstArticles: {
  96. edges: [{ node: testArticle }],
  97. },
  98. })
  99. })
  100. it('shows meta information if article is clicked', async () => {
  101. mockPermissions(['ticket.agent'])
  102. const view = await visitView('/tickets/1')
  103. expect(
  104. view.getByRole('heading', { name: 'Test Ticket', level: 2 }),
  105. ).toBeInTheDocument()
  106. expect(view.getByLabelText('Breadcrumb navigation')).toBeInTheDocument()
  107. expect(view.getByTestId('article-content')).toHaveTextContent('foobar')
  108. await view.events.click(view.getByTestId('article-bubble-body-1'))
  109. expect(
  110. await view.findByLabelText('Article meta information'),
  111. ).toBeInTheDocument()
  112. await view.events.click(view.getByTestId('article-bubble-body-1'))
  113. expect(
  114. view.queryByLabelText('Article meta information'),
  115. ).not.toBeInTheDocument()
  116. })
  117. it('shows checklist if it is enabled and user is agent', async () => {
  118. mockPermissions(['ticket.agent'])
  119. await mockApplicationConfig({ checklist: true })
  120. const view = await visitView('/tickets/1')
  121. await view.events.click(view.getByLabelText('Checklist'))
  122. expect(
  123. view.getByRole('heading', { name: 'Checklist', level: 2 }),
  124. ).toBeInTheDocument()
  125. })
  126. it('hides checklist if it is disabled and user is agent', async () => {
  127. mockPermissions(['ticket.agent'])
  128. await mockApplicationConfig({ checklist: false })
  129. const view = await visitView('/tickets/1')
  130. expect(
  131. view.queryByRole('heading', { name: 'Checklist', level: 2 }),
  132. ).not.toBeInTheDocument()
  133. })
  134. it('hides checklist if it is enabled and user is customer', async () => {
  135. mockPermissions(['ticket.customer'])
  136. await mockApplicationConfig({ checklist: true })
  137. const view = await visitView('/tickets/1')
  138. expect(
  139. view.queryByRole('heading', { name: 'Checklist', level: 2 }),
  140. ).not.toBeInTheDocument()
  141. })
  142. it('shows checklist ticket link for readonly agent', async () => {
  143. await mockApplicationConfig({ checklist: true })
  144. mockPermissions(['ticket.agent'])
  145. mockTicketChecklistQuery({
  146. ticketChecklist: {
  147. id: convertToGraphQLId('Checklist', 1),
  148. name: 'Checklist title',
  149. items: [
  150. {
  151. __typename: 'ChecklistItem',
  152. id: convertToGraphQLId('Checklist::Item', 2),
  153. text: 'Checklist item B',
  154. ticketAccess: null,
  155. checked: false,
  156. ticket: createDummyTicket(),
  157. },
  158. ],
  159. },
  160. })
  161. const view = await visitView('/tickets/1')
  162. await view.events.click(view.getByLabelText('Checklist'))
  163. const checklist = view.getByRole('heading', { name: 'Checklist', level: 2 })
  164. expect(checklist).toBeInTheDocument()
  165. // Checking display of ticket link
  166. expect(view.getByRole('link', { name: 'Test Ticket' })).toBeInTheDocument()
  167. // Ticket link has single item menu, hence we have to test it does not exist in readonly
  168. expect(
  169. within(checklist).queryByRole('button', { name: 'Remove item' }),
  170. ).not.toBeInTheDocument()
  171. })
  172. it('updates incomplete checklist item count', async () => {
  173. mockPermissions(['ticket.agent'])
  174. await mockApplicationConfig({ checklist: true })
  175. mockTicketChecklistQuery({
  176. ticketChecklist: {
  177. id: convertToGraphQLId('Checklist', 1),
  178. name: 'Checklist title',
  179. items: [
  180. { text: 'Item 1', checked: true, ticketAccess: null, ticket: null },
  181. { text: 'Item 2', checked: false, ticketAccess: null, ticket: null },
  182. ],
  183. incomplete: 1,
  184. },
  185. })
  186. const view = await visitView('/tickets/1')
  187. await view.events.click(view.getByLabelText('Checklist'))
  188. expect(
  189. view.getByRole('status', { name: 'Incomplete checklist items' }),
  190. ).toHaveTextContent('1')
  191. const checklistCheckboxes = view.getAllByRole('checkbox')
  192. await getTicketChecklistUpdatesSubscriptionHandler().trigger({
  193. ticketChecklistUpdates: {
  194. ticketChecklist: {
  195. id: convertToGraphQLId('Checklist', 1),
  196. name: 'Checklist title',
  197. items: [
  198. { text: 'Item 1', checked: true },
  199. { text: 'Item 2', checked: true },
  200. ],
  201. incomplete: 0,
  202. },
  203. },
  204. })
  205. expect(
  206. view.queryByRole('status', { name: 'Incomplete checklist items' }),
  207. ).not.toBeInTheDocument()
  208. // Click manually in the frontend again on one of the checklist to show
  209. // the incomplete state again(without a subscription = manual cache update).
  210. await view.events.click(checklistCheckboxes[1])
  211. expect(
  212. await view.findByRole('status', { name: 'Incomplete checklist items' }),
  213. ).toBeInTheDocument()
  214. })
  215. })