Vue-test-utils: [help-wanted] How to change a select element

Created on 13 Dec 2017  Â·  14Comments  Â·  Source: vuejs/vue-test-utils

I want to be able to test my Single File Vue Component by selecting an option in a Vue Child component. I need to trigger the Vue watcher that watches selectedFooItem. I am using mocha-webpack and js-global according to the vue-test-utils docs.

This is related to this issue: #128 , and my SO ticket. I also tried going the chat route but Discord tells me I don't have permission to send messages in that channel in vue-land.js.org.

I'm testing a Vue 2 component that has an el-select child component from ElementIO . The main difference in my example is that there is a child Vue component as opposed to HTML elements (i.e. can't rely on .find("select"). It looks like this:

// Foo.vue
<template lang="pug">
  .foo-item(:class="className")
    el-select(v-model="selectedFooItem",
              value-key="name",
              @click="onClick",
              @visible-change="onVisibleChange")
      el-option(v-for="entity in unselectedFooItems",
                :label="foo.name",
                :value="foo",
                :key="foo.name")
</template>

<script src="./Foo.js"></script>

What doesn't work:
1.

const selectBox = wrapper.find("el-select");
selectBox.trigger("click");
wrapper.find('el-option').trigger("click")

I see onClick being called twice (one from el-option). I think this refers to the event propagation behavior the issue I referenced was talking about. But, my watcher does not detect changes.

2)

wrapper.find('el-option').trigger("change")

Still, watcher does not detect changes.

3)

const selectBox = wrapper.find("el-select");
selectBox.trigger("change", { /* the value */ })

My work around

Right now I'm resorting to

wrapper.setData({ foo: 'bar' })

or

wrapper.vm.selectedFooItem = "bar"
question

Most helpful comment

To change the value of inputs and update data bound with v-model:

const input = wrapper.find('input[type="text"]')
input.element.value = 'some value' // input element value is changed, v-model is not
input.trigger('input') // v-model updated
const radioInput = wrapper.find('input[type="radio"]')
radioInput.element.checked = true // input element value is changed, v-model is not
radioInput.trigger('input') // v-model not updated
radioInput.trigger('change') // v-model updated
const radioInput = wrapper.find('input[type="radio"]')
radioInput.element.checked = true // input element value is changed, v-model is not
radioInput.trigger('change') // v-model updated
wrapper.findAll('option').at(1).element.selected = true 
wrapper.find('select').trigger('change')

There is an open issue to add helper methods to make this easier: #530

I'm closing this issue in favor of that feature request.

All 14 comments

If you call wrapper.update() after doing trigger does that make it work? It could be a sync/async issue.

I am trying to recreate this now, can you post the entire component and test? The issue says "how to change a select element" but you also mentioned a watcher.

Since watchers are async, you might need to use await, or call wrapper.update, for those to update.

Edit, had a shot at this. Used the element ui template on their website. I think you should be using the change event from element ui, not visible-change. Anyway... using the change event, I get the expected result in the browser - the new item. However, in vue-test-utils under Jest, I get an HTMLUnknownElement as the event.target. I guess some conflict between the library and the vue-test-utils?

The way I made it "work" in vue-test-utils is by adding click to the el-option - but this is not really doing what you want. See below.

I think setting data directly is actually okay, though. You don't need to test that el-select works, they have their own internal tests. However, I too have found some pain points when it comes to testing components using external libraries...

I thought about this a little more though, and I don't think we should unit testing external components anyway. They should have their own tests. vue-test-utils is for testing _your_ components, the code _you_ write. You can be confident el-select works, since the authors wrote tests for it already.

The test concerning "when the user selects something from a dropdown, an a watcher causes such and such to happen" sounds more like an integration test - much more high level.

Of course I am also new to unit testing and programming in general, so I could be wrong.

Here is what I tried regarding your initial question. Comments in the test (second snippet).

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <div>
      <el-select @change="handleChange" v-model="value" placeholder="Select">
        <el-option
           v-for="item in options"
           :key="item.value"
           @click="handleClick(item.value)"
           :label="item.label"
           :value="item.value">
        </el-option>
      </el-select>
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    handleClick (val) {
      this.value = val
    },

    handleChange (e) {
      console.log(e)
    }
  },

  data() {
    return {
      options: [{
        value: 'Option1',
        label: 'Option1'
      }, {
        value: 'Option2',
        label: 'Option2'
      }],
      value: ''
    }
  }
}
</script>
import { shallow, mount } from 'vue-test-utils'
import App from './App'

it('works?', () => {
  const wrapper = mount(App)

  // workaround...bad
  wrapper.findAll('el-option').at(0).trigger('click')

  // works in browser, but not in vue-test-utils.
  wrapper.findAll('el-select').at(0).trigger('change')
})

I hope that helps, please let me know if you have more questions or anything else.

I think for the 1.0 release we should provide some good documentation on _what_ to test, not just _how_.

@gsccheng, @lmiller1990: Have you figured it out?

@lmiller1990
While I agree that we shouldn't test external components, I don't think that it relates to this example, changing data directly is actually against practices promoted by vue-test-utils:

"we recommend writing tests that assert your component's public interface, and treat its internals as a black box."
(...)
"test case would simulate the click and assert that the rendered output has increased by 1"

So in this case, instead changing internal data we should rather simulate the change event. Otherwise handleChange method won't be covered. But frankly I've also found triggering change not working for radio inputs in vue-test-utils (I write specs in Jest).

Anyone figured out how to simulate selecting a radio input?

@folmert to check a radiobutton:

wrapper.find('#radioButton2').element.checked = true

@gsccheng Thanks for the issue. Are you able to make a minimal reproduction (a test that fails with the least amount of code as possible), and include all the code. Then I can have a look and debug. I can let you know how it's done.

@eddyerburgh it doesn't work, see my minimal reproduced example, running yarn test will return:

RadioGroup
√ renders changed data according to the changed folder data (38ms)
× renders changed data according to the selected folder option (8ms)

Expected value to be (using ===):
"Selected: existing"
Received:
"Selected:"

Spec:

import {mount} from 'vue-test-utils';
import RadioGroup from '@/components/RadioGroup';

describe('RadioGroup', () => {
    let wrapperDeep;

    beforeEach(() => {
        wrapperDeep = mount(RadioGroup, {});
    });

    // works
    it('changes data according to the changed folder data', () => {
        wrapperDeep.setData({folder: 'existing'});
        expect(wrapperDeep.find('.selected').text()).toBe('Selected: existing');
    });

    // doesn't work
    it('changes data according to the selected folder option', () => {
        wrapperDeep.find('input[type="radio"][value="existing"]').element.selected = true;
        expect(wrapperDeep.find('.selected').text()).toBe('Selected: existing');
    });
});

HI @folmert, you're right this is indeed an issue with model not picking up the change. Can you create a new issue for this please?

Ok, I've created a separate issue: https://github.com/vuejs/vue-test-utils/issues/345 but given that @gsccheng also fails to simulate option change the essence of the problem might be connected.

To change the value of inputs and update data bound with v-model:

const input = wrapper.find('input[type="text"]')
input.element.value = 'some value' // input element value is changed, v-model is not
input.trigger('input') // v-model updated
const radioInput = wrapper.find('input[type="radio"]')
radioInput.element.checked = true // input element value is changed, v-model is not
radioInput.trigger('input') // v-model not updated
radioInput.trigger('change') // v-model updated
const radioInput = wrapper.find('input[type="radio"]')
radioInput.element.checked = true // input element value is changed, v-model is not
radioInput.trigger('change') // v-model updated
wrapper.findAll('option').at(1).element.selected = true 
wrapper.find('select').trigger('change')

There is an open issue to add helper methods to make this easier: #530

I'm closing this issue in favor of that feature request.

It would be nice if all of these examples were in the documentation

There's a PR that will add methods to make this simpler—https://github.com/vuejs/vue-test-utils/pull/557

Okay. I also have one question. Is there a way to test that user can't change value in a disabled input?

For example

const wrapper = mount({
  data: () => ({ val: '' }),
  template: '<input v-model="val" disabled />'
});

wrapper.find('input').element.value = 'testing';
wrapper.find('input').trigger('input');

expect(wrapper.vm.val).toEqual('');
expect(wrapper.vm.val).not.toEqual('testing');

Or it's a browser's work - prevent input on a disabled input?

I believe JSDom supported disabled - does the above snippet not work?

Oh, I made a mistake. Snippet above does work.

But if I change disabled to readonly it does not work. Maybe it is okay.

Readonly does not prevent events from triggering, or JavaScript from filling the value. You can research disabled and readonly elsewhere.

Was this page helpful?
0 / 5 - 0 ratings