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.
Yes, you can setup watcher on computed property, see the fiddle.
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.
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.
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
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:
this
context to the Vue instance.this
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With