Abdulsamet ILERI
Abdulsamet ILERI

Reputation: 165

Testing a wrapped vuetify autocomplete component v menu does not open in html

I want to test my custom component via vue-test-utils. I use vuetify autocomplete component in this case. I just wrapped with div and extract as a custom component. Nothing to do special so far.

This component is really works on development. I have no problem with it.

I used this component as shown below.

<my-component
  id="brand-dropdown"
  ref="brand-dropdown"
  :items="brands"
  labelKey="product.brand"
  item-text="name"
  item-value="name"
  v-model="product.brandName"/>

My custom component looks like:

<template>
  <div class="comboContainer">
    <v-autocomplete
      class="product-combobox"
      ref="complete"
      :items="localItems"
      append-icon="$dropdownArrow"
      outlined
      dense
      hide-details
      color="#999999"
      :item-text="itemText"
      :item-value="itemValue"
      :filter="autoCompleteFilter"
      v-model="selected"
      @change="onListItemSelected">
      <template slot="label">
        <span>{{$t(labelKey)}}</span>
      </template>
      <template slot="prepend-inner" v-if="showSearchIcon">
        <div class="d-flex align-center justify-center"
             style="height: 25px;">
          <img src="~/assets/icons/search/search.png"/>
        </div>
      </template>
      <template v-slot:no-data>
        <v-list-item id="noMatchText">
          <v-list-item-title>
            <div :no-data-item="dataTestId">
              {{ $t('selection.noMatchFound') }}
            </div>
          </v-list-item-title>
        </v-list-item>
      </template>
      <template v-slot:item="{ item }">
        <v-tooltip top color="transparent">
          <template v-slot:activator="{ on, attrs }">
            <div
              v-on="item[itemText].length >36?on:null"
              class="combobox-item comboboxOverFlow"
              :data-testid="itemDataTestIdPrefix+item.id"
              :parent-list="dataTestId">
              <span>{{ item[itemText] }}</span>
            </div>
          </template>
          <div class="popup-rectangle">
            <span>{{ item[itemText] }}</span>
          </div>
        </v-tooltip>
      </template>
    </v-autocomplete>
  </div>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      default: [],
    },
    labelKey: {
      type: String,
      default: '',
    },
    itemText: {
      type: String,
      default: '',
    },
    itemValue: {
      type: String,
      default: '',
    },
    value: {
      type: [Number, String, Array],
    },
    disabled: {
      type: Boolean,
      default: false,
    }
    shouldTriggerWatch: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      selected: this.value,
      showSearchIcon: false,
    };
  },
  watch: {
    value: {
      immediate: true,
      handler(val) {
        if (this.shouldTriggerWatch) {
          this.selected = val;
          this.$emit('input', this.selected);
        }
      },
    },
  },
  computed: {
    localItems() {
      if (this.items && this.items.length) {
        return this.items.map(x => ({
          ...x,
          displayName: x.lang_key ? this.$t(x.lang_key) : x.name,
        }));
      }
      return [];
    },
  },
  methods: {
    onListItemSelected() {
      this.$emit('input', this.selected);
    },
    autoCompleteFilter(item, queryText, itemText) {
      const re = new RegExp(`(\\s+|^)(${queryText.toLocaleLowerCase()})(.*|$)`);
      return re.test(itemText.toLocaleLowerCase());
    },
  },
};

I want to test three test case.

  1. When I pass empty list should render as empty dropdown and render no-data slot.
      <template v-slot:no-data>
        <v-list-item id="noMatchText">
          <v-list-item-title>
            <div :no-data-item="dataTestId">
              {{ $t('selection.noMatchFound') }}
            </div>
          </v-list-item-title>
        </v-list-item>
      </template>
  1. When I pass filled list with test data I want to test all items rendered correctly
     <template v-slot:item="{ item }">
        <v-tooltip top color="transparent">
          <template v-slot:activator="{ on, attrs }">
            <div
              v-on="item[itemText].length >36?on:null"
              class="combobox-item comboboxOverFlow"
              :data-testid="itemDataTestIdPrefix+item.id"
              :parent-list="dataTestId">
              <span>{{ item[itemText] }}</span>
            </div>
          </template>
          <div class="popup-rectangle">
            <span>{{ item[itemText] }}</span>
          </div>
        </v-tooltip>
      </template>
  1. When I search I want to provide my filter function works as expected. And autocomplete renders items according to filtered value.

This is what my test file.

function mountComponent(options) {
  return mount(ComboBox, {
    vuetify: new Vuetify(),
    sync: false,
    mocks: {
      $t: key => key,
      $i18n: { locale: 'en' },
    },
    ...options,
  });
}

describe('Combobox unit tests', () => {
  beforeEach(() => {
    document.body.setAttribute('data-app', 'true');
  });

  test('should create component successfully', () => {
    const wrapper = mountComponent({ items: [] });
    expect(wrapper.exists()).toBeTruthy();
  });
  test('should list zero items if the item list is empty', async () => {
    const wrapper = mountComponent({
      propsData: {
        items: [],
        labelKey: 'labelKey',
        dataTestId: 'test-dropdown',
        itemText: 'name',
        itemValue: 'id',
      },
    });

    const autocomplete = wrapper.find('.product-combobox');
    const autocompleteControls = autocomplete.find('.v-input__slot');
    autocompleteControls.trigger('click');

    await wrapper.vm.$nextTick();
    await flushPromises();

    **// v menu cant opened !!!!**
  });
  test('should list third items correctly', async () => {
    const testItems = [{ name: 'item1', id: 1 }, { name: 'item2', id: 2 }, { name: 'item3', id: 3 }];
    const wrapper = mountComponent({
      attachToDocument: true,
      propsData: {
        eagerProp: true,
        items: testItems,
        dataTestId: 'test-dropdown',
        itemDataTestIdPrefix: 'test-dropdown-item-',
        itemText: 'name',
        itemValue: 'id',
        value: null,
      },
    });

    const slot = wrapper.find('.v-input__slot');
    const input = wrapper.find('input');

    // Focus input should only focus
    input.trigger('focus');

    expect(wrapper.vm.$children[0].isFocused).toBe(true);
    expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
    expect(wrapper.vm.$children[0].isMenuActive).toBe(false);

    slot.trigger('click');

    expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
    expect(wrapper.vm.$children[0].menuCanShow).toBe(true);

    wrapper.setProps({ searchInput: 'foo' });

    expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
    expect(wrapper.vm.$children[0].menuCanShow).toBe(true);

    // v menu cant opened !!!!
    // you think these expects is unnecesary. you are right I just explore this component 
    // if I success I'll delete all of them
  });
  test('should filter autocomplete search results', async () => {
    
  });
});

I can't open v-menu in test enviroment.

I tried emit events and trigger('click') emitted events

When I console log wrapper.html() I cant see v menu is opened and all items rendered in html. Output is shown below.

<div class="comboContainer">
   <div class="v-input product-combobox v-input--hide-details v-input--dense theme--light v-text-field v-text-field--enclosed v-text-field--outlined v-select v-autocomplete">
      <div class="v-input__control">
         <div role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-owns="list-2" class="v-input__slot" style="height: 44px;">
            <fieldset aria-hidden="true">
               <legend style="width: 0px;"><span>​</span></legend>
            </fieldset>
            <div class="v-select__slot">
               <label for="input-2" class="v-label theme--light" style="left: 0px; position: absolute;"><span></span></label><input data-testid="test-dropdown" id="input-2" type="text" autocomplete="off">
               <div class="v-input__append-inner">
                  <div class="v-input__icon v-input__icon--append"><i aria-hidden="true" class="v-icon notranslate material-icons theme--light">$dropdownArrow</i></div>
               </div>
               <input type="hidden">
            </div>
            <div class="v-menu"></div>
         </div>
      </div>
   </div>
</div>

My problem is where is items? <div class="v-menu"></div> Why I cant see in wrapper.html. I don't use shallowMount, I use mount. Because of this I cannot write my covered test cases.

How can I simulate opened menu and rendered all items and provide some assertions?

What am I missing?

Versions

"@nuxtjs/vuetify": "1.12.1",
"@vue/test-utils": "1.0.0-beta.29",
"vue-jest": "3.0.7"

Upvotes: 0

Views: 1785

Answers (1)

Dmitry Sharshakov
Dmitry Sharshakov

Reputation: 828

Well, probably the better way would be mocking and substituting v-autocomplete with a test component (just control values that go to it and emit fake events from it). You are not developing Vuetify, so no need to test what happens inside a component.

Upvotes: 1

Related Questions