Search code examples
javascriptvue.jsjestjsvuetify.jsv-autocomplete

vue.js - Test watchers of v-autocomplete


I have a vue.js SearchTest component with a v-autocomplete. It has watchers for select and search:

// SearchTest.vue

<template>
  <v-autocomplete
    v-model="select"
    :items="items"
    :loading="isLoading"
    :search-input.sync="search"
    hide-no-data
    hide-selected
    placeholder="Type search query"
    return-object
  ></v-autocomplete>
</template>

<script>
export default {
  data: () => ({
    select: null,
    isLoading: false,
    search: null,
    items: [],
  }),
  watch: {
    select(val) {
      console.log('select: ', val);
    }
    search(val) {
      console.log('search: ', val);
    },
  },
};
</script>

<style></style>

I'm testing it as following. I expect both watchers should fire. In fact, only the select watcher fired. The search watcher did not.

// SearchTest.spec.js

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

import Vue from 'vue'
import Vuetify from 'vuetify'

Vue.use(Vuetify)

describe('SearchTest.vue', () => {
  const localVue = createLocalVue();
  const vuetify = new Vuetify({});
  let spy;
  beforeAll(() => {
    spy = jest.spyOn(console, 'log');
  })

  afterEach(() => {
    spy.mockClear();
  })

  it('Select', () => {
    const wrapper = mount(SearchTest, { localVue, vuetify });

    // Triggers select.
    wrapper.vm.select = 'select';
    wrapper.vm.$nextTick(() => {
      expect(spy).toBeCalled();  // OK
    });
  })

  it('Search', () => {
    const wrapper = mount(SearchTest, { localVue, vuetify });

    // Should trigger search, but fails.
    wrapper.vm.search = 'search';
    wrapper.vm.$nextTick(() => {
      expect(spy).toBeCalled();  // Fail
    });
  })
})

Any idea for passing the Search test?


Solution

  • I don't know exactly what is happening, but I found that the v-autocomplete internally sets the search property to null on creation if no item is selected (source). It does so by using this.$nextTick, which I suppose causes problems when setting the search value inside the test. I found two ways to mitigate this:

    1

    Nesting the change of search and the assertion into another $nextTick seems to the trick.

    it('Search', () => {
      const wrapper = mount(SearchTest, { localVue, vuetify });
    
      wrapper.vm.$nextTick(() => {
        wrapper.vm.search = 'search';
        wrapper.vm.$nextTick(() => {
          expect(spy).toBeCalled();
        });
      });
    })
    

    2

    For this solution I took the initialisation of wrapper and put it in beforeEach. It does basically the same but I guess the timing of the mounting of the component is different, so it doesn't collide with the $nextTick of v-autocomplete.

    describe('SearchTest.vue', () => {
      const localVue = createLocalVue();
      const vuetify = new Vuetify({});
      let spy;
      let wrapper;
    
      beforeEach(() => {
        wrapper = mount(SearchTest, { localVue, vuetify });
      })
    
      beforeAll(() => {
        spy = jest.spyOn(console, 'log');
      })
    
      afterEach(() => {
        spy.mockClear();
      })
    
      it('Select', () => {
        // Triggers select.
        wrapper.vm.select = 'select';
        wrapper.vm.$nextTick(() => {
          expect(spy).toBeCalled();  // OK
        });
      })
    
      it('Search', () => {
        // Should trigger search, but fails.
        wrapper.vm.search = 'search';
        wrapper.vm.$nextTick(() => {
          expect(spy).toBeCalled();  // Fail
        });
      })
    })