Просмотр исходного кода

Maintenance: Unify mocking implementation for provide & inject.

Co-authored-by: Benjamin Scharf <bs@zammad.com>
Benjamin Scharf 4 месяцев назад
Родитель
Сommit
9bb25ac036

+ 10 - 18
app/frontend/apps/desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/AddMenu/__tests__/AddMenu.spec.ts

@@ -1,6 +1,6 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { computed, provide } from 'vue'
+import { computed } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
 import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
@@ -9,28 +9,17 @@ import { mockPermissions } from '#tests/support/mock-permissions.ts'
 import AddMenu from '#desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/AddMenu/AddMenu.vue'
 import { COLLAPSED_STATE_KEY } from '#desktop/components/layout/LayoutSidebar/LeftSidebar/useCollapsedState.ts'
 
-const renderAddMenu = () =>
-  renderComponent(
-    {
-      template: `<AddMenu/>`,
-      components: { AddMenu },
-      setup() {
-        provide(
-          COLLAPSED_STATE_KEY,
-          computed(() => true),
-        )
-      },
-    },
-    { router: true },
-  )
-
 describe('AddMenu', () => {
   describe('create ticket action button', () => {
     it('renders action button ', () => {
       mockPermissions(['ticket.agent', 'ticket.customer'])
       mockApplicationConfig({ customer_ticket_create: true })
 
-      const wrapper = renderAddMenu()
+      const wrapper = renderComponent(AddMenu, {
+        router: true,
+        provide: [[COLLAPSED_STATE_KEY, computed(() => true)]],
+      })
+
       expect(wrapper.getByLabelText('New ticket')).toBeInTheDocument()
     })
 
@@ -38,7 +27,10 @@ describe('AddMenu', () => {
       mockPermissions(['ticket.customer'])
       mockApplicationConfig({ customer_ticket_create: false })
 
-      const wrapper = renderAddMenu()
+      const wrapper = renderComponent(AddMenu, {
+        router: true,
+        provide: [[COLLAPSED_STATE_KEY, computed(() => true)]],
+      })
 
       expect(wrapper.queryByLabelText('New ticket')).not.toBeInTheDocument()
     })

+ 9 - 18
app/frontend/apps/desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/AdminMenu/__tests__/AdminMenu.spec.ts

@@ -1,6 +1,6 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { computed, provide } from 'vue'
+import { computed } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
 import { mockPermissions } from '#tests/support/mock-permissions.ts'
@@ -8,27 +8,15 @@ import { mockPermissions } from '#tests/support/mock-permissions.ts'
 import AdminMenu from '#desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/AdminMenu/AdminMenu.vue'
 import { COLLAPSED_STATE_KEY } from '#desktop/components/layout/LayoutSidebar/LeftSidebar/useCollapsedState.ts'
 
-const renderAdminMenu = () =>
-  renderComponent(
-    {
-      template: `<AdminMenu/>`,
-      components: { AdminMenu },
-      setup() {
-        provide(
-          COLLAPSED_STATE_KEY,
-          computed(() => true),
-        )
-      },
-    },
-    { router: true },
-  )
-
 describe('AdminMenu', () => {
   describe('create ticket action button', () => {
     it('renders setting button ', () => {
       mockPermissions(['admin.monitoring'])
 
-      const wrapper = renderAdminMenu()
+      const wrapper = renderComponent(AdminMenu, {
+        provide: [[COLLAPSED_STATE_KEY, computed(() => true)]],
+        router: true,
+      })
 
       expect(wrapper.getByLabelText('Administration')).toBeInTheDocument()
     })
@@ -36,7 +24,10 @@ describe('AdminMenu', () => {
     it('does not renders setting button if user has not permission', () => {
       mockPermissions(['ticket.agent'])
 
-      const wrapper = renderAdminMenu()
+      const wrapper = renderComponent(AdminMenu, {
+        provide: [[COLLAPSED_STATE_KEY, computed(() => true)]],
+        router: true,
+      })
 
       expect(wrapper.queryByLabelText('Administration')).not.toBeInTheDocument()
     })

+ 9 - 18
app/frontend/apps/desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/__tests__/MenuContainer.spec.ts

@@ -1,7 +1,7 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
 import { expect } from 'vitest'
-import { computed, provide } from 'vue'
+import { computed } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
 import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
@@ -10,27 +10,15 @@ import { mockPermissions } from '#tests/support/mock-permissions.ts'
 import MenuContainer from '#desktop/components/layout/LayoutSidebar/LeftSidebar/MenuContainer/MenuContainer.vue'
 import { COLLAPSED_STATE_KEY } from '#desktop/components/layout/LayoutSidebar/LeftSidebar/useCollapsedState.ts'
 
-const renderMenuContainer = (collapsed = false) =>
-  renderComponent(
-    {
-      template: `<MenuContainer/>`,
-      components: { MenuContainer },
-      setup() {
-        provide(
-          COLLAPSED_STATE_KEY,
-          computed(() => collapsed),
-        )
-      },
-    },
-    { router: true },
-  )
-
 describe('ActionMenu', () => {
   it('renders container with two action menus', () => {
     mockPermissions(['ticket.agent', 'ticket.customer', 'admin'])
     mockApplicationConfig({ customer_ticket_create: true })
 
-    const wrapper = renderMenuContainer()
+    const wrapper = renderComponent(MenuContainer, {
+      router: true,
+      provide: [[COLLAPSED_STATE_KEY, computed(() => false)]],
+    })
 
     expect(wrapper.getAllByRole('listitem')).toHaveLength(2)
 
@@ -43,7 +31,10 @@ describe('ActionMenu', () => {
     mockPermissions(['ticket.agent', 'ticket.customer', 'admin'])
     mockApplicationConfig({ customer_ticket_create: true })
 
-    const wrapper = renderMenuContainer(true)
+    const wrapper = renderComponent(MenuContainer, {
+      router: true,
+      provide: [[COLLAPSED_STATE_KEY, computed(() => true)]],
+    })
 
     expect(wrapper.getByRole('list')).toHaveClass('flex-col')
   })

+ 10 - 22
app/frontend/apps/desktop/pages/ticket/components/TicketDetailView/TicketDetailTopBar/TopBarHeader/TicketInformation/TicketInformationBadgeList/__tests__/ChecklistBadgeList.spec.ts

@@ -1,7 +1,6 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { cleanup } from '@testing-library/vue'
-import { type App, computed, ref } from 'vue'
+import { computed, ref } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
 import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
@@ -22,8 +21,8 @@ vi.mock('#desktop/pages/ticket/composables/useTicketSidebar.ts', () => ({
   }),
 }))
 
-const mockTicket = (app: App, ticket: TicketById) => {
-  app.provide(TICKET_KEY, {
+const mockTicket = (ticket: TicketById) => {
+  return {
     ticketId: computed(() => ticket.id),
     ticket: computed(() => ticket),
     form: ref(),
@@ -31,27 +30,16 @@ const mockTicket = (app: App, ticket: TicketById) => {
     isTicketEditable: computed(() => true),
     newTicketArticlePresent: ref(false),
     ticketInternalId: computed(() => ticket.internalId),
-  })
+  }
 }
 
 describe('TicketChecklistBadges', () => {
-  afterEach(() => {
-    // :TODO write a cleanup inside of the renderComponent to avoid
-    // :ERROR App already provides property with key "Symbol(ticket)". It will be overwritten with the new value
-    // Missing cleanup in test env
-    // It is still getting logged as warnings
-    cleanup()
-    vi.clearAllMocks()
-  })
-
   it('hides badges if no checklist is present', () => {
     const data = createDummyTicket()
 
     const wrapper = renderComponent(ChecklistBadgeList, {
       router: true,
-      global: {
-        plugins: [(app) => mockTicket(app, data)],
-      },
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
     expect(wrapper.queryByTestId('common-badge')).not.toBeInTheDocument()
   })
@@ -69,7 +57,7 @@ describe('TicketChecklistBadges', () => {
 
     const wrapper = renderComponent(ChecklistBadgeList, {
       router: true,
-      plugins: [(app) => mockTicket(app, data)],
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
 
     expect(wrapper.getByIconName('checklist')).toBeInTheDocument()
@@ -92,7 +80,7 @@ describe('TicketChecklistBadges', () => {
 
     const wrapper = renderComponent(ChecklistBadgeList, {
       router: true,
-      plugins: [(app) => mockTicket(app, data)],
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
 
     await wrapper.events.click(
@@ -131,7 +119,7 @@ describe('TicketChecklistBadges', () => {
     })
 
     const wrapper = renderComponent(ChecklistBadgeList, {
-      plugins: [(app) => mockTicket(app, data)],
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
 
     const badges = wrapper.getAllByTestId('common-badge')
@@ -180,7 +168,7 @@ describe('TicketChecklistBadges', () => {
 
     const wrapper = renderComponent(ChecklistBadgeList, {
       router: true,
-      plugins: [(app) => mockTicket(app, data)],
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
 
     await wrapper.events.click(
@@ -223,7 +211,7 @@ describe('TicketChecklistBadges', () => {
 
     const wrapper = renderComponent(ChecklistBadgeList, {
       router: true,
-      plugins: [(app) => mockTicket(app, data)],
+      provide: [[TICKET_KEY, mockTicket(data)]],
     })
 
     expect(

+ 17 - 23
app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarCustomer/__tests__/TicketSidebarCustomer.spec.ts

@@ -1,6 +1,5 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { cleanup } from '@testing-library/vue'
 import { computed, ref } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
@@ -36,19 +35,23 @@ const renderTicketSidebarCustomer = async (
       },
     },
     router: true,
-    plugins: [
-      (app) => {
-        const ticket = createDummyTicket()
-        app.provide(TICKET_KEY, {
-          ticketId: computed(() => ticket.id),
-          ticket: computed(() => ticket),
-          form: ref(),
-          showTicketArticleReplyForm: () => {},
-          isTicketEditable: computed(() => true),
-          newTicketArticlePresent: ref(false),
-          ticketInternalId: computed(() => ticket.internalId),
-        })
-      },
+    provide: [
+      [
+        TICKET_KEY,
+        () => {
+          const ticket = createDummyTicket()
+
+          return {
+            ticketId: computed(() => ticket.id),
+            ticket: computed(() => ticket),
+            form: ref(),
+            showTicketArticleReplyForm: () => {},
+            isTicketEditable: computed(() => true),
+            newTicketArticlePresent: ref(false),
+            ticketInternalId: computed(() => ticket.internalId),
+          }
+        },
+      ],
     ],
     global: {
       stubs: {
@@ -66,15 +69,6 @@ const renderTicketSidebarCustomer = async (
 }
 
 describe('TicketSidebarCustomer.vue', () => {
-  afterEach(() => {
-    // :TODO write a cleanup inside of the renderComponent to avoid
-    // :ERROR App already provides property with key "Symbol(ticket)". It will be overwritten with the new value
-    // Missing cleanup in test env
-    // It is still getting logged as warnings
-    cleanup()
-    vi.clearAllMocks()
-  })
-
   it('shows sidebar when customer ID is present', async () => {
     const wrapper = await renderTicketSidebarCustomer({
       formValues: {

+ 6 - 15
app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarCustomer/__tests__/TicketSidebarCustomerContent.spec.ts

@@ -1,6 +1,5 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { cleanup } from '@testing-library/vue'
 import { computed, ref } from 'vue'
 
 import { renderComponent } from '#tests/support/components/index.ts'
@@ -99,9 +98,10 @@ const renderTicketSidebarCustomerContent = async (
         screenType: screen,
       },
     },
-    plugins: [
-      (app) => {
-        app.provide(TICKET_KEY, {
+    provide: [
+      [
+        TICKET_KEY,
+        {
           ticketId: computed(() => ticket.id),
           ticket: computed(() => ticket),
           form: ref(),
@@ -109,23 +109,14 @@ const renderTicketSidebarCustomerContent = async (
           isTicketEditable: computed(() => true),
           newTicketArticlePresent: ref(false),
           ticketInternalId: computed(() => ticket.internalId),
-        })
-      },
+        },
+      ],
     ],
     router: true,
     ...options,
   })
 
 describe('TicketSidebarCustomerContent.vue', () => {
-  afterEach(() => {
-    // :TODO write a cleanup inside of the renderComponent to avoid
-    // :ERROR App already provides property with key "Symbol(ticket)". It will be overwritten with the new value
-    // Missing cleanup in test env
-    // It is still getting logged as warnings
-    cleanup()
-    vi.clearAllMocks()
-  })
-
   beforeEach(() => {
     mockPermissions(['ticket.agent'])
   })

+ 15 - 16
app/frontend/apps/desktop/pages/ticket/components/TicketSidebar/TicketSidebarInformation/__tests__/TicketSidebarInformationContent.spec.ts

@@ -1,7 +1,5 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
-import { cleanup } from '@testing-library/vue'
-import { afterEach } from 'vitest'
 import { computed, ref } from 'vue'
 
 import renderComponent from '#tests/support/components/renderComponent.ts'
@@ -16,6 +14,15 @@ import { TicketSidebarScreenType } from '#desktop/pages/ticket/types/sidebar.ts'
 
 const defaultTicket = createDummyTicket()
 
+vi.mock('vue-router', async () => {
+  const mod = await vi.importActual<typeof import('vue-router')>('vue-router')
+
+  return {
+    ...mod,
+    onBeforeRouteUpdate: vi.fn(),
+  }
+})
+
 const renderInformationSidebar = (ticket = defaultTicket) =>
   renderComponent(TicketSidebarInformationContent, {
     props: {
@@ -26,9 +33,10 @@ const renderInformationSidebar = (ticket = defaultTicket) =>
     },
     form: true,
     router: true,
-    plugins: [
-      (app) => {
-        app.provide(TICKET_KEY, {
+    provide: [
+      [
+        TICKET_KEY,
+        {
           ticketId: computed(() => ticket.id),
           ticket: computed(() => ticket),
           form: ref(),
@@ -36,22 +44,13 @@ const renderInformationSidebar = (ticket = defaultTicket) =>
           isTicketEditable: computed(() => true),
           newTicketArticlePresent: ref(false),
           ticketInternalId: computed(() => ticket.internalId),
-        })
-      },
+        },
+      ],
     ],
   })
 
 describe('TicketSidebarInformationContent', () => {
   describe('actions', () => {
-    afterEach(() => {
-      // :TODO write a cleanup inside of the renderComponent to avoid
-      // :ERROR App already provides property with key "Symbol(ticket)". It will be overwritten with the new value
-      // Missing cleanup in test env
-      // It is still getting logged as warnings
-      cleanup()
-      vi.clearAllMocks()
-    })
-
     it('displays basic sidebar content', () => {
       mockPermissions(['ticket.agent'])
 

+ 1 - 9
app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/__tests__/ArticleList.spec.ts

@@ -1,7 +1,6 @@
 // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
 
 import { renderComponent } from '#tests/support/components/index.ts'
-import { getTestPlugins } from '#tests/support/components/renderComponent.ts'
 
 import type { TicketArticle } from '#shared/entities/ticket/types.ts'
 import { convertToGraphQLId } from '#shared/graphql/utils.ts'
@@ -40,14 +39,7 @@ it('renders delivery messages', () => {
       articles,
       totalCount: 1,
     },
-    global: {
-      plugins: [
-        ...getTestPlugins(),
-        (app) => {
-          app.provide(TICKET_INFORMATION_SYMBOL, {})
-        },
-      ],
-    },
+    provide: [[TICKET_INFORMATION_SYMBOL, {}]],
   })
 
   expect(view.container).toHaveTextContent('Delivery failed')

+ 30 - 2
app/frontend/tests/support/components/renderComponent.ts

@@ -8,9 +8,21 @@ import userEvent from '@testing-library/user-event'
 import { render } from '@testing-library/vue'
 import { mount } from '@vue/test-utils'
 import { merge, cloneDeep } from 'lodash-es'
-import { isRef, nextTick, ref, watchEffect, unref } from 'vue'
+import { afterEach, vi } from 'vitest'
+import {
+  isRef,
+  nextTick,
+  ref,
+  watchEffect,
+  unref,
+  type App,
+  type Plugin,
+  type Ref,
+} from 'vue'
 import { createRouter, createWebHistory } from 'vue-router'
 
+import type { DependencyProvideApi } from '#tests/support/components/types.ts'
+
 import CommonAlert from '#shared/components/CommonAlert/CommonAlert.vue'
 import CommonBadge from '#shared/components/CommonBadge/CommonBadge.vue'
 import CommonDateTime from '#shared/components/CommonDateTime/CommonDateTime.vue'
@@ -48,7 +60,6 @@ import buildLinksQueries from './linkQueries.ts'
 
 import type { Matcher, RenderResult } from '@testing-library/vue'
 import type { ComponentMountingOptions } from '@vue/test-utils'
-import type { Plugin, Ref } from 'vue'
 import type { Router, RouteRecordRaw, NavigationGuard } from 'vue-router'
 
 const appName = getTestAppName()
@@ -109,6 +120,7 @@ export interface ExtendedMountingOptions<Props>
   store?: boolean
   confirmation?: boolean
   form?: boolean
+  provide?: DependencyProvideApi
   formField?: boolean
   unmount?: boolean
   dialog?: boolean
@@ -428,6 +440,18 @@ const setupVModel = <Props>(wrapperOptions: ExtendedMountingOptions<Props>) => {
   }
 }
 
+const mockProvide = (app: App, provideApi: DependencyProvideApi) => {
+  provideApi.forEach((dependency) => {
+    const [key, data] = dependency
+    // App globals get reused in each test run we have to clear the provides in each test
+    if (app._context.provides[key]) {
+      app._context.provides[key] = data
+    } else {
+      app.provide(key, data)
+    }
+  })
+}
+
 const renderComponent = <Props>(
   component: any,
   wrapperOptions: ExtendedMountingOptions<Props> = {},
@@ -478,6 +502,10 @@ const renderComponent = <Props>(
     delete wrapperOptions.plugins
   }
 
+  if (wrapperOptions.provide) {
+    plugins.push((app: App) => mockProvide(app, wrapperOptions.provide!))
+  }
+
   const { startWatchingModel } = setupVModel(wrapperOptions)
 
   const localWrapperOptions: ExtendedMountingOptions<Props> = merge(

+ 5 - 0
app/frontend/tests/support/components/types.ts

@@ -0,0 +1,5 @@
+// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+type DependencyProvide = [symbol | string, any]
+
+export type DependencyProvideApi = DependencyProvide[]