nidkil
nidkil

Reputation: 1415

How to test Vuetify VHover?

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?

Upvotes: 1

Views: 3662

Answers (3)

Tomer Epstein
Tomer Epstein

Reputation: 1

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()
  })
})

Upvotes: 0

sknight
sknight

Reputation: 2256

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')
  })
})

Upvotes: 1

tony19
tony19

Reputation: 138336

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
})

Upvotes: 1

Related Questions