GrayedFox
GrayedFox

Reputation: 2553

How do I trigger a v-autocomplete "input" event with vue test utils?

I'm trying to trigger the "input" event of a Vuetify v-autocomplete component inside of a unit test. From what I can tell, the unit test is quite similar to the one the Vuetify guys are using, but for some reason the mock method is never called.

The factory method simply returns a component instance made via mount and adds a local store, vue, and vueLoading instance.

Would love a bit of insight here, already lost several hours to this and I'm starting to go a bit mad...

Vuetify version: 1.4.3

Component

<template>
  <v-autocomplete
    :items="options"
    :value="selectedId"
    :label="label || $t('user.filter')"
    :dark="dark"
    :class="fieldClass"
    :menu-props="{ contentClass }"
    :loading="$loading.isLoading('fetch users')"
    item-text="email"
    item-value="id"
    name="search_by_user"
    hide-details
    single-line
    clearable
    @input="updateValue($event)"
    @click.native="fetchUsers"
  />
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  props: {
    value: {
      type: String,
      required: false,
      default: null
    },
    label: {
      type: String,
      required: false,
      default: null
    },
    dark: {
      type: Boolean,
      required: false,
      default: true
    },
    fieldClass: {
      type: String,
      required: false,
      default: 'select-user-search'
    },
    contentClass: {
      type: String,
      required: false,
      default: 'user-search'
    },
    blankItem: {
      type: Object,
      required: false,
      default: null
    }
  },
  data () {
    return {
      selectedId: this.value
    }
  },
  computed: {
    ...mapGetters(['users']),
    options () { return this.blankItem ? [this.blankItem].concat(this.users) : this.users }
  },
  created () {
    if (this.value) this.fetchUsers()
  },
  methods: {
    fetchUsers () {
      if (this.users.length) return

      this.$store.dispatch('FETCH_USERS', {
        fields: ['id', 'email'],
        filter: { active: 'true' }
      })
    },
    updateValue (value) {
      this.selectedId = value
      this.$emit('input', value)
    }
  }
}
</script>

<style>
  .select-user-search {
    overflow: hidden;
    padding-bottom: 1px;
  }

  .select-user-search .v-select__selections {
    overflow: hidden;
  }

  .select-user-search .v-select__selection {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
</style>

Unit Test

import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import VueLoading from 'vuex-loading'
import Vuetify from 'vuetify'
import UserSearch from 'manager/components/user/search.vue'

const factory = (values, shallow = true) => {
  if (!shallow) {
    return mount(UserSearch, { ...values })
  }
  return shallowMount(UserSearch, { ...values })
}

const localVue = createLocalVue()

localVue.use(Vuex)
localVue.use(VueLoading)
localVue.use(Vuetify)

describe('UserSearch', () => {
  let actions, getters, store, vueLoading

  beforeEach(() => {
    actions = {
      FETCH_USERS: jest.fn()
    }

    getters = {
      users: () => []
    }

    vueLoading = new VueLoading({ useVuex: true })

    store = new Vuex.Store({
      actions,
      getters
    })
  })

  it('calls "updateValue" method when input triggered', async () => {
    const methods = {
      updateValue: jest.fn()
    }

    const wrapper = factory({ methods, store, localVue, vueLoading }, false)
    const input = wrapper.find('input')

    input.element.value = 'value'
    input.trigger('input')

    await wrapper.vm.$nextTick()

    expect(methods['updateValue']).toBeCalledWith('value')
  })
})

Upvotes: 2

Views: 5008

Answers (1)

tkarpenko
tkarpenko

Reputation: 21

I use the following variant

Component

<template>
    <span> <!-- span (or the other HTML element) around <v-autocomplete> is important -->
        <v-autocomplete
            :items="options"
            :value="selectedId"
            :label="$t('user.filter')"
            :menu-props="{ contentClass }"
            :loading="false"
            item-text="email"
            item-value="id"
            name="search_by_user"
            hide-details
            single-line
            clearable
            @input="updateValue($event)"
            @click.native="fetchUsers"
        />
    </span>
</template>


<script lang="ts">
    import { Component } from 'vue-property-decorator';
    import Vue from 'vue';

    @Component
    export default class AppAutocomplete extends Vue {
        private options: any[] = [{email: '[email protected]', id: 1}];
        private selectedId: string|undefined = undefined;
        private contentClass = 'user-search';

        private fetchUsers() {
        }

        public updateValue(event: any) {
        }
    }
</script>

Unit test

import { Wrapper, createLocalVue, mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuetify from 'vuetify';

// @ts-ignore
import AppAutocomplete from '@/components/AppAutocomplete.vue';


describe('AppAutocomplete', () => {
    Vue.use(Vuetify);

    let wrapper: Wrapper<Vue>;
    let vm: any;

    beforeEach(() => {

        wrapper = mount(AppAutocomplete, {
            localVue: createLocalVue(),
            vuetify: new Vuetify({
                mocks: { $vuetify: { lang: { t: (val: string) => val } } },
            }),
            mocks: { $t: () => { /** */ } },
        });

        vm = wrapper.vm;
    });


    afterEach(() => wrapper.destroy());


    it(`calls "updateValue" method when input triggered`, ()  => {

        jest.spyOn(vm, 'updateValue');

        const autocompleteElem = wrapper.find('.v-autocomplete');

        autocompleteElem.vm.$emit('input', 'box1');

        expect(vm.updateValue).toBeCalledTimes(1);
        expect(vm.updateValue).toHaveBeenCalledWith('box1');

    });

});

To surround v-autocomplete by some other HTML element is important. Without it a test fails.

Vuetify version: 2.1.12

Upvotes: 2

Related Questions