0

I am trying to write a basic unit test for a component called CompanyBioPanel. Here is a simplified version of the component:

// template

    <v-card-text>
      <v-tabs v-model="companyTabs">
        <v-tab value="generalData">
          <h2>Details</h2>
        </v-tab>
        <v-tab value="companyConfiguration">
          <h2>Company Configuration</h2>
        </v-tab>
      </v-tabs>
      <v-divider />
      <v-window
        v-if="companyData.company_id != null"
        v-model="companyTabs"
      >
        <v-window-item value="generalData">
          <v-list>
            <company-data-item
              v-for="item in generalData"
              :key="item.label"
              :label="item.label"
              :value="item.value"
            />
          </v-list>
        </v-window-item>
        <v-window-item value="companyConfiguration">
          <v-list>
            <company-data-item
              v-for="item in companyConfiguration"
              :key="item.label"
              :label="item.label"
              :value="item.value"
            />
          </v-list>
        </v-window-item>
      </v-window>
    </v-card-text>

// script
import type { Company } from './types'
import {
  computed,
  toRefs,
  ref,
  type ComputedRef
} from 'vue'
import CompanyDataItem from './CompanyDataItem.vue'

const props = defineProps<{ companyData: { [key: string]: string } }>()

const companyTabs = ref(null)

const { companyData } = toRefs(props)

const companyIdOrNull = computed(() => {
  return companyData.value?.company_id ?? null
})

const generalData: ComputedRef<CompanyDataItemProps[]> = computed(() => {
  if (companyData.value == null) {
    return []
  }

  return [
    {
      label: 'Name',
      value: companyData.value.name,
      fieldName: 'name',
      isEditable: true
    }
  ]
})

In my test, I would like to assert that when I pass the companyData object as a prop to my component, it renders the company-data-item component.

Here is my test:

import { mount } from '@vue/test-utils'
import { h } from 'vue'
import { VApp } from 'vuetify/components'
import CompanyBioPanel from './CompanyBioPanel.vue'

it('renders the company bio panel', async () => {
  const companyData = {
    company_id: 'abc',
    name: 'Test Company'
  }

  const wrapper = mount(VApp, { slots: { default: h(CompanyBioPanel, { props: { companyData } }) } })

  expect(wrapper.exists()).toBe(true)
  const html = wrapper.html()
  expect(html).toContain('Test Company')

  const companyName = wrapper.find('h1')
  expect(companyName.text()).toBe('Test Company')

  const companyDataItems = wrapper.findAllComponents({ name: 'company-data-item' })
  expect(companyDataItems.length).toBe(1)
})

This is a simple test asserting that child components are generated from data passed as props. However, I get the following error when I attempt to run my test:

⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  src/components/company-view/company-bio-panel/CompanyBioPanel.test.ts > renders the company bio panel
TypeError: Cannot read properties of undefined (reading 'company_id')
 ❯ src/components/company-view/company-bio-panel/CompanyBioPanel.vue:45:27
     43|       <v-divider />
     44|       <v-window
     45|         v-if="companyData.company_id != null"
       |                           ^
     46|         v-model="companyTabs"
     47|       >

This doesn't make sense to me, because I am passing the props in my test, so how could companyData be undefined?

I've also tried modifying the way I pass props to be like this:

  const wrapper = mount(VApp, { slots: { default: h(CompanyBioPanel, { companyData }) } })

but that leads to an even stranger error:

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
TypeError: Cannot create property '_destroyed' on number '-1'
 ❯ clearImmediate node:timers:325:24
 ❯ GlobalWindow.cancelAnimationFrame ../../node_modules/happy-dom/src/window/Window.ts:921:10
 ❯ node_modules/vuetify/lib/components/VSlideGroup/VSlideGroup.mjs:104:9
 ❯ callWithErrorHandling ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:156:18
 ❯ callWithAsyncErrorHandling ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:164:17
 ❯ job ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1831:9
 ❯ flushPreFlushCbs ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:308:7
 ❯ updateComponentPreRender ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5876:5
 ❯ ReactiveEffect.componentUpdateFn [as fn] ../../node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5794:11
 ❯ ReactiveEffect.run ../../node_modules/@vue/reactivity/dist/reactivity.cjs.js:182:19

1 Answer 1

1

You could create a Vuetify instance :

const vuetify = createVuetify()

And Use mount with global.plugins to properly integrate Vuetify;

const wrapper = mount(VApp, { 
    global: {
      plugins: [vuetify],
    },
    slots: {
      default: () => h(CompanyBioPanel, { companyData }) // Correctly passing props
    } 
  })

And then if you wait for the next DOM update this will ensure all asynchronous updates have been processed before making assertions. :

await wrapper.vm.$nextTick();

And this should pass the props correctly

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer, with your example I get that UnhandledRejection error, so I guess it must be a bug with Vuetify or something. I'll keep digging.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.