How to test `input`-based component responds to user input

Hi there

I’m trying to test a custom component which represents an in-place editable piece of text (think about card titles on Trello, for example). I’ve successfully tested that the component reacts appropriately to clicks, etc., but I cannot work out how to test that it updates its bound model. I want to write a test something along the lines of

it('updates the model on user input', () => {
  const initialValue = 'Initial value';
  const wrapper = shallowMount(EditableLabel, { propsData: { initialValue } });

  const newValue = 'Updated value';
  // Next line is pseudocode
  wrapper.find('input').setValue(newValue);

  expect(wrapper.vm.$data.value).to.equal(newValue);
});

but everything I’ve tried hasn’t made any difference and the expected value is always equivalent to the initial value.

What am I missing?

2 Likes

Something along the lines of the following should work:

const input =  wrapper.find('input')
input.element.value = newValue
input.trigger('change') // input // click

See: https://vue-test-utils.vuejs.org/api/wrapper/#trigger-eventtype-options (end of page)

2 Likes

Thanks @LinusBorg, much appreciated. This seems to display the same behaviour as my earlier attempts: the initial value is not overwritten. Furthermore, I see an error from Typescript (v2.8) stating that value does not exist on HTMLElement. innerText and nodeValue, which do exist, also don’t update the value of the model.

would you mind sharing the implementation that you are trying to test? I’m currently kind of guessing what you are tring to test against.

Sure thing, sorry. Here’s my template and (Typescript) SFC:

<template>
  <div v-on:click="enableEdit">
    <span v-show="!edit">{{ value }} <i class="fas fa-pencil-alt"></i></span>
    <form v-on:submit.prevent="disableEdit" v-show="edit">
      <input v-model="value" v-on:blur="disableEdit" />
    </form>
  </div>
</template>
import { Vue, Component, Prop } from 'vue-property-decorator';

@Component
export default class EditableLabel extends Vue {
  @Prop() public initialValue!: string;
  private edit: boolean = false;

  public data(): any {
      return {
        value: this.initialValue,
      };
  }

  public enableEdit(): void {
      this.edit = true;
  }

  public disableEdit(): void {
      this.edit = false;
  }
}

Hm, I think what I did should work, except it should be an input event, not a change event.

1 Like

Yup, works fine with input.element.value and triggering input :+1: Thanks so much!

So I updated this to work with v-model as per the documentation, and the test has stopped passing again :confounded: I don’t really understand why this is, though, as the input element still handles input events as before, and as per the documentation, emits the input event as well. I guess the difference is that I’m no longer setting the value in data, but checking against wrapper.vm.$props.value also indicates there is no change to the value (to be expected, I guess), and I don’t really know where else to look. wrapper.text() also indicates the value has not changed.

Here’s the updated code:

<div v-on:click="enableEdit">
  <span v-show="!edit">{{ value }} <i class="fas fa-pencil-alt"></i></span>
  <form v-on:submit.prevent="disableEdit" v-show="edit">
    <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" v-on:blur="disableEdit" />
  </form>
</div>
@Component
export default class EditableLabel extends Vue {
  @Prop() public value!: string;
  private edit: boolean = false;

  public enableEdit(): void {
      this.edit = true;
  }

  public disableEdit(): void {
      this.edit = false;
  }
}

and the test:

it('updates the model on user input', () => {
    const newValue = 'A new extractor';
    const input = wrapper.find('input');

    (wrapper.find('input').element as HTMLInputElement).value = newValue;
    input.trigger('input');

    expect(wrapper.text()).to.contain(newValue);
  });

FWIW, I’m very happy to contribute documentation changes to make this clearer, but I don’t really properly understand what’s going on at the moment to make those changes.

1 Like

I have part-answered my question, in that I think the behaviour has changed here: the expectation should now be that the input event is emitted from the component to update the v-model binding, and so I have added an assertion reflecting this change. What I haven’t managed to do successfully is write an assertion indicating that the component updates its local value, so that the value interpolated in the <span> matches the value entered into the <input>.

I had the same problem. It seemed that the value of the input was never written.
Try to set the value differently.
This worked for me:
wrapper.find(‘input’).setValue(100);

1 Like

Thank you!