Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test computed property in Vue.js using AVA with Avoriaz

I'm trying to test a computed property of a Vue.js component using AVA and Avoriaz. I can mount the component and access the data properties fine.

When I try an access a computed property, the function doesn't seem to have scope to the data on that component.

computed: {
  canAdd() {
    return this.crew.firstName !== '' && this.crew.lastName !== '';
  }

The error I get is Error: Cannot read property 'firstName' of undefined

Test file:

import Vue from 'vue';
import { mount }
from 'avoriaz';
import test from 'ava';
import nextTick from 'p-immediate';
import ComputedPropTest from '../../../js/vue-components/computed_prop_test.vue';

Vue.config.productionTip = false;

test.only('Should handle computed properties', async(t) => {
  const MOCK_PROPS_DATA = {
      propsData: {
        forwardTo: '/crew',
        crew: {}
      }
    },
    wrapper = mount(ComputedPropTest, MOCK_PROPS_DATA),
    DATA = {
      crew: {
        firstName: 'Ryan',
        lastName: 'Gill'
      }
    };

  wrapper.setData(DATA);
  await nextTick();

  console.log('firstName: ', wrapper.data().crew.firstName); // Ryan

  console.log('isTrue: ', wrapper.computed().isTrue()); // true
  console.log('canAdd: ', wrapper.computed().canAdd()); // Errors

  t.true(wrapper.computed().isTrue());
});

Component:

<template>
  <div>
    <label for="firstName" class="usa-color-text-primary">First Name
      <i class="tooltipTextIcon fa fa-info-circle usa-color-text-gray" title="First name of crew."></i>
      <span class="required usa-additional_text usa-color-text-secondary-dark">Required</span>
    </label>
    <input id="firstName" type="text" class="requiredInput" name="firstName" v-model="crew.firstName" autofocus>
    <label for="lastName" class="usa-color-text-primary">Last Name
      <i class="tooltipTextIcon fa fa-info-circle usa-color-text-gray" title="Last name of crew."></i>
      <span class="required usa-additional_text usa-color-text-secondary-dark">Required</span>
    </label>
    <input id="lastName" type="text" class="requiredInput" name="lastName" v-model="crew.lastName" autofocus>
  </div>
</template>

<script>
  export default {
    name: 'crew-inputs',
    data() {
      return {
        crew: {
          firstName: '',
          lastName: ''
        }
      }
    },
    computed: {
      canAdd() {
        return this.crew.firstName !== '' && this.crew.lastName !== '';
      },
      isTrue() {
        return true;
      }
    }
  }
</script>

The isTrue computed property seems to work but doesn't rely on any of the data in the component.

like image 241
Ryan Gill Avatar asked Mar 13 '17 16:03

Ryan Gill


People also ask

Can you watch a computed property Vue?

Yes, you can setup watcher on computed property, see the fiddle.

How do you write unit test for computed properties in Vue?

spec. js import Vue from 'vue' import MyComponent from 'Component. vue' describe("Component test", function() { var myComponentVar = new Vue(MyComponent); var vm = myComponentVar. $mount(); beforeEach(function() { vm = myComponentVar.

How computed properties work Vue?

A computed property is used to declaratively describe a value that depends on other values. When you data-bind to a computed property inside the template, Vue knows when to update the DOM when any of the values depended upon by the computed property has changed.


1 Answers

Problem

What is happening?

After a long look and discussion, it looks like the this context of the computed getter is being set to something unexpected. As a result of the unexpected this context, this no longer refers to the Vue instance, leading to component properties being unaccessible.

You are witnessing this with the runtime error

Error: Cannot read property 'firstName' of undefined

Why is this happening?

Without a deep dive into how Avoriaz and Vue are working, we cannot know. I did attempt a deeper investigation with the following minimal, complete and verifiable example. You or others may want to take a deeper look into it.

'use-strict';

import Vue from 'vue';
import { mount } from 'avoriaz';

const FooBar = {
  template: `
    <div>{{ foobar }}</div>
  `,

  data() {
    return {
      foo: 'foo',
      bar: 'bar',
    };
  },

  computed: {
    foobar() {
      debugger;
      return `${this.foo} ${this.bar}`;
    },
  },
};

const vueMountedCt = new Vue(FooBar).$mount();
const vueMountedVm = vueMountedCt;

const avoriazMountedCt = mount(FooBar);
const avoriazMountedVm = avoriazMountedCt.vm;

/**
 * Control case, accessing component computed property in the usual way as documented by Vue.
 *
 * @see {@link https://vuejs.org/v2/guide/computed.html}
 *
 * Expectation from log: 'foobar' (the result of the computed property)
 * Actual result from log: 'foobar'
 */
console.log(vueMountedVm.foobar);

/**
 * Reproduce Avoriaz's method of accessing a Vue component's computed properties.
 * Avoriaz returns the Vue instance's `$option.computed` when calling `wrapper.computed()`.
 *
 * @see {@link https://github.com/eddyerburgh/avoriaz/blob/9882f286e7476cd51fe069946fee23dcb2c4a3e3/src/Wrapper.js#L50}
 *
 * Expectation from log: 'foobar' (the result of the computed property)
 * Actual result from log: 'undefined undefined'
 */
console.log(vueMountedVm.$options.computed.foobar());

/**
 * Access Vue component computed property via Avoriaz's documented method.
 *
 * @see {@link https://eddyerburgh.gitbooks.io/avoriaz/content/api/mount/computed.html}
 *
 * Expectation from log: 'foobar' (the result of the computed property)
 * Actual result from log: 'undefined undefined'
 */
console.log(avoriazMountedCt.computed().foobar());

Some observations:

  • Looking at the call stack of control case (case 1), you can see Vue's internals setting the this context to the Vue instance.

Call stack of case 1. Getter function's <code>this</code> is being set to Vue instance

  • Looking at the call stack of the failing cases, the this context of the computed function is not being set.

Call stack of failing cases. The <code>this</code> context of the computed function is not being set

As to why this is happening – I have no idea. To understand this I think we will need to know why vm.$options.computed exists, the planned use cases from the core Vue team and if the behaviour we are experiencing is expected.

What can I do about this?

You can work around this by doing

wrapper.computed().canAdd.call(wrapper.vm);

It may also be recommended you open issues in Avoriaz and/or Vue.

like image 88
Wing Avatar answered Nov 04 '22 19:11

Wing