1

I have created the following component that wraps Vuetify VHover, VTooltip and VBtn to simplify my app.

<template>
  <div>
    <v-hover v-if="tooltip">
      <v-tooltip
        slot-scope="{ hover }"
        bottom
      >
        <v-btn
          slot="activator"
          :theme="theme"
          :align="align"
          :justify="justify"
          :disabled="disabled"
          :depressed="type === 'depressed'"
          :block="type === 'block'"
          :flat="type === 'flat'"
          :fab="type === 'fab'"
          :icon="type === 'icon'"
          :outline="type === 'outline'"
          :raised="type === 'raised'"
          :round="type === 'round'"
          :color="hover ? colorHover : color"
          :class="{ 'text-capitalize': label, 'text-lowercase': icon }"
          :size="size"
          @click="onClick()"
        >
          <span v-if="label">{{ label }}</span>
          <v-icon v-else>{{ icon }}</v-icon>
        </v-btn>
        <span>{{ tooltip }}</span>
      </v-tooltip>
    </v-hover>
    <v-hover v-else>
      <v-btn
        slot-scope="{ hover }"
        :theme="theme"
        :align="align"
        :justify="justify"
        :disabled="disabled"
        :depressed="type === 'depressed'"
        :block="type === 'block'"
        :flat="type === 'flat'"
        :fab="type === 'fab'"
        :icon="type === 'icon'"
        :outline="type === 'outline'"
        :raised="type === 'raised'"
        :round="type === 'round'"
        :color="hover ? colorHover : color"
        :class="{ 'text-capitalize': label, 'text-lowercase': icon }"
        :size="size"
        @click="onClick()"
      >
        <span v-if="label">{{ label }}</span>
        <v-icon v-else>{{ icon }}</v-icon>
      </v-btn>
    </v-hover>
  </div>
</template>

<script>
import VueTypes from 'vue-types'
export default {
  name: 'v-btn-plus',
  props: {
    align: VueTypes.oneOf(['bottom', 'top']),
    justify: VueTypes.oneOf(['left', 'right']),
    color: VueTypes.string.def('primary'),
    colorHover: VueTypes.string.def('secondary'),
    disabled: VueTypes.bool.def(false),
    icon: VueTypes.string,
    label: VueTypes.string,
    position: VueTypes.oneOf(['left', 'right']),
    tooltip: VueTypes.string,
    size: VueTypes.oneOf(['small', 'medium', 'large']).def('small'),
    theme: VueTypes.oneOf(['light', 'dark']),
    type: VueTypes.oneOf(['block', 'depressed', 'fab', 'flat', 'icon', 'outline', 'raised', 'round']).def('raised')
  },
  methods: {
    onClick() {
      this.$emit('click')
    }
  },
  created: function() {
    // Workaround as prop validation on multiple props is not possible
    if (!this.icon && !this.label) {
      console.error('[Vue warn]: Missing required prop, specify at least one of the following: "label" or "icon"')
    }
  }
}
</script>

<style scoped>
</style>

I want to test VHover and VTooltip and have defined the following spec file.

import { createLocalVue, mount } from '@vue/test-utils'
import Vuetify from 'vuetify'
import VBtnPlus from '@/components/common/VBtnPlus.vue'

describe('VStatsCard.vue', () => {
  let localVue = null

  beforeEach(() => {
    localVue = createLocalVue()
    localVue.use(Vuetify)
  })

  it('renders with default settings when only label is specified', async () => {
    const label = 'Very cool'
    const defaultColor = 'primary'
    const defaultType = 'raised'
    const defaultSize = 'small'
    const wrapper = mount(VBtnPlus, {
      localVue: localVue,
      propsData: { label }
    })
    expect(wrapper.text()).toMatch(label)
    expect(wrapper.html()).toContain(`${defaultType}="true"`)
    expect(wrapper.html()).toContain(`size="${defaultSize}"`)
    expect(wrapper.html()).toContain(`class="v-btn theme--light ${defaultColor} text-capitalize"`)
    expect(wrapper.html()).not.toContain(`v-icon"`)
    wrapper.find('button').trigger('mouseenter')
    await wrapper.vm.$nextTick()
    const btnHtml = wrapper.find('.v-btn').html()
    expect(btnHtml).toContain('secondary')
    expect(btnHtml).not.toContain('primary')
    const tooltipId = btnHtml.match(/(data-v-.+?)(?:=)/)[1]
    const tooltips = wrapper.findAll('.v-tooltip_content')
    let tooltipHtml = null
    for (let tooltip of tooltips) {
      const html = tooltip.html()
      console.log(html)
      if (html.indexOf(tooltipId) > -1) {
        tooltipHtml = html
        break
      }
    }
    expect(tooltipHtml).toContain('menuable_content_active')
  })
})

The wrapper.find('button').trigger('mouseenter') does not work as expected. When I look at the html code after the nextTick it is the same as before trigger was called. It looks like I'm missing a part of the html. I was excpecting to see the following html.

<div data-v-d3e326b8="">
   <span data-v-d3e326b8="" class="v-tooltip v-tooltip--bottom">
      <span>
         <button data-v-d3e326b8="" type="button" class="v-btn v-btn--depressed theme--light orange text-lowercase" size="small">
            <div class="v-btn__content"><i data-v-d3e326b8="" aria-hidden="true" class="v-icon mdi mdi-account theme--light"></i></div>
         </button>
      </span>
   </span>
</div>

All I'm getting is the <button> part.

Any suggestions how to get this to work?

tony19
  • 125,647
  • 18
  • 229
  • 307
nidkil
  • 1,295
  • 1
  • 17
  • 28
  • For interest sake what is the outcome for testing the hover component, since Vuetify already has test for their components. – Jeremy Walters Jan 21 '19 at 09:39
  • Good question :-) For one I want to understand how it works with vue-test-utils and two I want to ensure that I can change my component with confidence and upgrade Vuetify and now that my component still works because the tests pass. – nidkil Jan 21 '19 at 21:28
  • In my opinion, if your are using a third party library like vuetify. I would leave the tests in their hands. Before a Major update they clearly indicate which changes is breaking changes. If you want to create test, it would be much more useful to spent that time on your core application, rather than on third party lib which already have test. I really encourage having tests! But useful tests. Everything I have mentioned is based on personal feelings and experiences. – Jeremy Walters Jan 21 '19 at 21:40

3 Answers3

1

Explicitly triggering mouseenter events doesn't normally have an effect, likely because browsers ignore simulated/non-trusted events. Instead, you could set v-hover's value to true to force the hover state:

VBtnPlus.vue

<template>
  <div>
    <v-hover :value="hovering">...</v-hover>
  </div>
</template>

<script>
export default {
  data() {
    return {
      hovering: false,
    }
  },
  //...
}
</script>

VBtnPlus.spec.js

it('...', async () => {
  wrapper.vm.hovering = true;
  // test for hover state here
})
tony19
  • 125,647
  • 18
  • 229
  • 307
1

I just ran into this myself using Nuxt, Vuetify, and Jest. I followed this example for Vuetify 2.x.

Below is a very simple example of what I did with my code.

As a side note, when I tried to set the wrapper.vm.[dataField] = [value] directly, Jest threw an error not allowing direct set access on the object. In the example.spec.js below, calling wrapper.setData({...}) will allow you to set the data value without any issue.

example.vue:

<template lang="pug">
  v-hover(
    v-slot:default="{ hover }"
    :value="hoverActive"
  )
    v-card#hoverable-card(
      :elevation="hover ? 20 : 10"
    )
</template>

<script>
export default {
  data() {
    return {
      hoverActive: false
    }
  }
</script>

example.spec.js

import { mount, createLocalVue } from '@vue/test-utils'
import Vuetify from 'vuetify'
import example from '@/components/example.vue'

const localVue = createLocalVue()
localVue.use(Vuetify)

describe('example', () => {
  const wrapper = mount(example, {
    localVue
  })

  it('should have the correct elevation class on hover', () => {
    let classes = wrapper.classes()
    expect(classes).toContain('elevation-10')
    expect(classes).not.toContain('elevation-20')

    wrapper.setData({ hoverActive: true })
    classes = wrapper.classes()
    expect(classes).not.toContain('elevation-10')
    expect(classes).toContain('elevation-20')
  })
})
sknight
  • 2,009
  • 1
  • 14
  • 15
  • Thanks. So you are adding the "hoverActive" variable into the view just for testing purposes? Or did you have hoverActive in your class already for another reason? – Greg Veres May 21 '20 at 13:47
  • The v-slot:default="{ hover }" listens for hovering over the generated element. I created the template based on the Vuetify documentation. One of the issues I ran into was I wasn't able to get the v-hover to work with a mimicked mouseover in the test. However, I know that I could change the linked data value and verify the class change. However, to answer your question, this is a simplified sample of my template and the test used to test the template. – sknight May 21 '20 at 22:50
0

https://github.com/vuejs/vue-test-utils/issues/1421

it('2. User interface provides one help icon with tooltip text', async (done) => {
  // stuff
  helpIcon.trigger('mouseenter')
  await wrapper.vm.$nextTick()
  requestAnimationFrame(() => {
    // assert
    done()
  })
})