How to test a vue vuetify v-autocomplete - vue.js

Somehow I'm not able to see the items of the v-autocomplete inside the html during the test and also setting an item does not work. Below the v-autocomplete (which is loading the items on mount) and the test.
// v-autocomplete.vue
<template>
<v-autocomplete
:items="items"
item-text="name"
item-value="id"
></v-autocomplete>
</template>
<script>
export default {
data() {
return {
items: [],
};
},
mounted() {
service.getItems().then((response) => {
this.items = response;
});
},
};
</script>
The test looks like this:
//v-autocomplete.spec.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import VAutocomplete from '#/components/v-autocomplete.vue';
import '#testing-library/jest-dom';
import { createLocalVue, mount } from '#vue/test-utils';
import service from '#/service/service';
Vue.use(Vuetify);
const items= [
{ name: 'Item 1', id: 1 },
{ name: 'Item 2', id: 2 },
];
const getItemsSpy = jest
.spyOn(service, 'getItems')
.mockImplementation(() => {
return new Promise((resolve) => {
resolve(items);
});
});
describe('v-autocomplete.vue', () => {
let localVue;
let vuetify;
let wrapper;
beforeEach(() => {
jest.clearAllMocks();
localVue = createLocalVue();
vuetify = new Vuetify();
});
it('should load the items', async () => {
wrapper = mount(VAutocomplete, {
localVue,
vuetify,
propsData: {},
});
expect(getItemsSpy).toBeCalledTimes(1);
for (const item of items) {
expect(getByText(wrapper.element, item.name)).toBeTruthy(); // Will fail -> Unable to find an element with the text
}
});
});
The test expect(getByText(wrapper.element, item.name)).toBeTruthy(); will fail with the message Unable to find an element with the text. Also printing the html with console.log(wrapper.html()) does not show the items of the v-autocomplete. So my question would be:
How could I test, if the items were loaded?
How could I set one of the loaded as the selected item of the v-autocomplete?

You can open autocomplete menu on click .v-input__slot. I do no think, it is best practice to test library functionality.
Unit test:
it("should load the items", async () => {
wrapper = mount(VAutocomplete, {
localVue,
vuetify,
propsData: {}
});
const autocomplete = wrapper.element;
const autocompleteControls = autocomplete.find(".v-input__slot");
autocompleteControls.trigger("click");
await wrapper.vm.$nextTick();
expect(getItemsSpy).toBeCalledTimes(1);
for (const item of items) {
expect(getByText(wrapper.element, item.name)).toBeTruthy();
}
});
Source:
https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/\_\_tests__/VSelect.spec.ts

Related

Jest : TypeError: Cannot read property 'variable' of undefined

I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
List.spec.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import { createLocalVue, shallowMount } from '#vue/test-utils';
import List from '#/components/home/list.vue';
Vue.use(Vuetify);
describe('List.vue', () => {
const localVue = createLocalVue();
let vuetify;
const $t = () => {};
const localePath = () => {};
beforeEach(() => {
vuetify = new Vuetify();
localVue.use(vuetify);
});
const mockOrder = [
{
coardshare: {
cs_id: 123,
},
},
{
talkboard: {
cs_id: 123,
},
},
];
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data() {
return {
data: mockOrder,
};
},
});
expect(wrapper.html()).toMatchSnapshot();
const title = wrapper.find('.v-card__title > span');
expect(title.text()).toBe('Foobar');
});
});
List.vue
<template>
...
<div v-for="item in state.data.talkboard" :key="item.cs_id">
<ListItem :item="item"></ListItem>
</div>
...
</template>
<script>
import { reactive, onMounted, useContext } from '#nuxtjs/composition-api';
import axios from 'axios';
import Header from './header';
import ListItem from './list-item.vue';
export default {
name: 'ListHome',
components: {
Header,
ListItem,
},
setup() {
const state = reactive({
data: [],
});
const { store } = useContext();
const fatch = async () => {
....
};
onMounted(fatch);
return {
state,
fatch,
};
},
};
</script>
error message
TypeError: Cannot read property 'data' of undefined
I am testing though Jest on the Vue 2.x, nuxtjs and #nuxtjs/composition-api.
However, the state value in the components has undefined value when testing though jest
why error on this ?? because of composition API that define the state with reactive() function ??
In your test file maybe you can try something like this:
it('11111', () => {
const wrapper = shallowMount(List, {
localVue,
vuetify,
propsData: { data: mockOrder },
mocks: { $t, localePath },
data: () => {
return {
data: mockOrder,
};
},
});

Testing vue watchers with vue-testing-library

Anyone know how I would test a watcher in a component with vue-testing-library?
Here is my component. I want to test that the method is called when the brand vuex state is updated. With vue test utils it would be easy but I have not found a good way to do this with vue testing library. Has anyone did this before using vue testing library.
<template>
<v-data-table
data-testid="builds-table"
:headers="headers"
:items="builds"
:items-per-page="10"
class="elevation-1"
:loading="loading"
>
<template v-slot:[getItemStatus]="{ item }">
<v-chip :color="getStatusColor(item.status)" dark>
{{ item.status }}
</v-chip>
</template>
</v-data-table>
</template>
<script>
import { mapState } from "vuex";
import { getScheduledBuilds } from "../services/buildActivationService";
import { getStatusColor } from "../utils/getStatusColor";
export default {
name: "BuildsTable",
data() {
return {
loading: false,
headers: [
{
text: "Activation Time",
align: "start",
value: "buildActivationTime",
},
{ text: "Build ID", value: "buildId" },
{ text: "Build Label", value: "buildLabel" },
{ text: "Status", value: "status" },
],
error: "",
};
},
async mounted() {
this.getBuilds();
},
computed: {
...mapState(["brand", "builds"]),
getItemStatus() {
return `item.status`;
},
},
watch: {
brand() {
this.getBuilds();
},
},
methods: {
getStatusColor(status) {
return getStatusColor(status);
},
async getBuilds() {
try {
this.loading = true;
const builds = await getScheduledBuilds(this.$store.getters.brand);
this.$store.dispatch("setBuilds", builds);
this.items = this.$store.getters.builds;
this.loading = false;
} catch (error) {
this.loading = false;
this.error = error.message;
this.$store.dispatch("setBuilds", []);
}
},
},
};
</script>
Vue Testing Library is just a wrapper for Vue Test Utils, so the same call verification techniques apply.
Here's how to verify the call with Jest and Vue Testing Library:
Spy on the component method definition before rendering the component:
import { render } from '#testing-library/vue'
import BuildsTable from '#/components/BuildsTable.vue'
const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
render(BuildsTable)
Render the component with a given store and a callback to capture the Vuex store instance under test:
let store = {
state: {
brand: '',
builds: [],
}
}
const storeCapture = (_, vuexStore) => store = vuexStore
render(BuildsTable, { store }, storeCapture)
Update the store's brand value, and wait a macro tick for the watcher to take effect, then verify the getBuilds spy is called twice (once in mounted() and again in the brand watcher):
store.state.brand = 'foo'
await new Promise(r => setTimeout(r)) // wait for effect
expect(getBuilds).toHaveBeenCalledTimes(2)
The full test would look similar to this:
import { render } from '#testing-library/vue'
import BuildsTable from '#/components/BuildsTable.vue'
describe('BuildsTable.vue', () => {
it('calls getBuilds when brand changes', async() => {
const getBuilds = jest.spyOn(BuildsTable.methods, 'getBuilds')
let store = {
state: {
brand: '',
builds: [],
}
}
const storeCapture = (_, vuexStore) => store = vuexStore
render(BuildsTable, { store }, storeCapture)
store.state.brand = 'foo'
await new Promise(r => setTimeout(r)) // wait for effect
expect(getBuilds).toHaveBeenCalledTimes(2)
})
})

Vue Test Utils - Unable to correctly mount/shallow mount component, wrapper undefined

I've tried almost everything I can think of but I'm unable to correctly mount/shallow mount my vue components for testing correctly. Everytime I console.log the wrapper I get the following print out:
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: []
}
This question is similar to this question asked here:
Vue-test-utils wrapper undefined
I'm using Vuetify, Vuex and Vue Router. My test.spec.ts is below:
import { shallowMount, createLocalVue, mount } from "#vue/test-utils"
import Vuex from "vuex"
import Vuetify from "vuetify"
import VueRouter from "vue-router"
import TheExamAnswer from "#/components/common/TheExamAnswer.vue"
describe("TheExamAnswer.vue", () => {
const localVue = createLocalVue()
let getters: any
let store: any
let vuetify: any
let router: any
beforeEach(() => {
localVue.use(Vuex)
localVue.use(Vuetify)
localVue.use(VueRouter)
getters = {
getExam: () => true,
}
store = new Vuex.Store({
modules: {
// Need to add FlightPlanning for name spacing
FlightPlanning: {
namespaced: true,
getters,
},
},
})
vuetify = new Vuetify()
router = new VueRouter()
})
it("Renders the element if the exam has been submitted", () => {
const wrapper = mount(TheExamAnswer, { localVue, store, router })
console.log("This is the HTML", wrapper.html())
expect(wrapper.text()).toContain("Show Answer")
})
})
My view component is very simple and the code is below:
<template>
<div v-if="submitted" class="div">
<v-btn #click="answerHidden = !answerHidden" class="mb-10"
>Show Answer</v-btn
>
<div v-if="!answerHidden">
<slot name="questionAnswer"></slot>
</div>
</div>
</template>
<script>
export default {
data: () => {
return {
answerHidden: true,
}
},
computed: {
submitted() {
const exam = this.$store.getters["FlightPlanning/getExam"]
return exam.submitted
},
},
}
</script>
<style></style>
UPDATED: I've added the suggestion from the answer below however now I"m getting the following message.
TheExamAnswer.vue
✕ Renders the element if the exam has been submitted (49ms)
● TheExamAnswer.vue › Renders the element if the exam has been submitted
expect(received).toContain(expected) // indexOf
Expected substring: "Show Answer"
Received string: ""
38 | const wrapper = mount(TheExamAnswer, { localVue, store, router })
39 | console.log("This is the HTML", wrapper.html())
> 40 | expect(wrapper.text()).toContain("Show Answer")
| ^
41 | })
42 | })
43 |
at Object.it (tests/unit/test.spec.ts:40:28)
console.error node_modules/vuetify/dist/vuetify.js:43612
[Vuetify] Multiple instances of Vue detected
See https://github.com/vuetifyjs/vuetify/issues/4068
If you're seeing "$attrs is readonly", it's caused by this
console.log tests/unit/test.spec.ts:39
This is the HTML
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
As you can see the HTML is blank and therefore I'm presuming that's also the same reason it's failing this test as the received string is "".
SOLUTION -
I figured it out. The error was on my behalf by not looking at the logic of the computed property correctly.
In my test I had:
getters = {
getExam: () => true,
}
In my component I had:
computed: {
submitted() {
const exam = this.$store.getters["FlightPlanning/getExam"]
return exam.submitted
},
If you look at the logic of the computed property it going to take whats returned from the getter and assign it to the exam variable. Originally I was returning true, because that's what I wanted the submitted() function to return this means when I call exam.submitted I'm calling it on a boolean value which obviously gives me "undefined". The solution was to return exactly what the computed property was designed to deal with, an object i.e. {submitted:true}
Therefore the final test looks like this and is returning valid HTML.
import { shallowMount, createLocalVue, mount } from "#vue/test-utils"
import Vuex from "vuex"
import Vuetify from "vuetify"
import VueRouter from "vue-router"
import TheExamAnswer from "#/components/common/TheExamAnswer.vue"
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Vuetify)
localVue.use(VueRouter)
describe("test.vue", () => {
let getters: any
let store: any
let vuetify: any
let router: any
beforeEach(() => {
getters = {
getExam: () => {
return { submitted: true }
},
}
store = new Vuex.Store({
modules: {
// Need to add FlightPlanning for name spacing
FlightPlanning: {
namespaced: true,
getters,
},
},
})
vuetify = new Vuetify()
router = new VueRouter()
})
it("Renders the element if the exam has been submitted", () => {
const wrapper = mount(TheExamAnswer, { localVue, vuetify, store, router })
console.log("This is the HTML", wrapper.html())
})
})
This gives me the result of:
console.log tests/unit/test.spec.ts:44
This is the HTML <div><button type="button" class="mb-10 v-btn v-btn--contained theme--light v-size--default"><span class="v-btn__content">Show Answer</span></button>
<!---->
</div>
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.235s
Ran all test suites.
The console.log with that weird input for wrapper or elements is normal behaviour. I ran couple tests with your components and everything was working.
it("Renders the element if the exam has been submitted", () => {
const wrapper = mount(TheExamAnswer, { localVue, store, router });
expect(wrapper.text()).toContain("Show Answer");
});
If you want to console.log html in your wrapper:
console.log(wrapper.html())
UPDATED: the reason, why wrapper.html() return empty string is v-if="submitted" on your root component. The computed property return undefined, because getter return true, so true.submitted return undefined
Getter in test.spec.ts:
getters = {
getExam: () => {
return { submitted: true };
}
};

Stub telephone input jest vue JS

I'm trying to stub a tel input plugin I'm using in one of my Vue component.
This is what I've tried, I think I almost got it but I'm missing one small detail.
This is my spec file:
import { mount, createLocalVue } from '#vue/test-utils';
import ModalNewCustomer from "../../../resources/js/subviews/customers/components/ModalNewCustomer";
// Stubs
const telInput = {
template: `<input type="tel">`
}
import moment from 'moment';
describe('Modal New Customer',() => {
let wrapper,mockCheckNameDuplicate;
let districts = [
{"id":1,"name":"Aberdeen"},
{"id":2,"name":"Admiralty"},
{"id":3,"name":"Ap Lei Chau"},
{"id":4,"name":"Beacon Hill"},
{"id":5,"name":"Braemar Hill"},
{"id":6,"name":"Causeway Bay"},
{"id":8,"name":"Central"}
];
let newCustomerData ={
firstName: 'Loulou',
lastName: 'Le Loup',
emailAddress: 'email#google.com',
sex: 'M',
primaryPhoneNumber: '+85200001111',
secondaryPhoneNumber: '+85233334444',
birthday: "2020-11-30",
wayOfContact: 'email',
electroOrNot: 1,
asiaMilesMembership: '00001111'
};
beforeEach(() => {
mockCheckNameDuplicate = jest.spyOn(ModalNewCustomer.methods, 'checkNameDuplicate');
wrapper = mount(ModalNewCustomer,{
localVue,
propsData:{
districts: districts
},
data(){
return{
COMPANY_NAME: 'HK'
}
},
router,
stubs:{
"vue-tel-input": telInput
}
});
flushPromises();
jest.resetModules();
jest.clearAllMocks();
});
afterEach(() => {
flushPromises();
jest.resetModules();
jest.clearAllMocks();
})
it('can input new customer data and submit it',async() => {
const {vm} = wrapper;
let primaryPhoneNumberInput = wrapper.find('#primaryPhoneNumber');
await primaryPhoneNumberInput.setValue(newCustomerData.primaryPhoneNumber)
expect(primaryPhoneNumberInput.element.value).toBe(newCustomerData.primaryPhoneNumber);
console.log(primaryPhoneNumberInput.html())
console.log(vm.newCustomer.primaryPhoneNumber)
});
});
In my vue component, the vue tel input looks like this:
<vue-tel-input
id="primaryPhoneNumber"
:default-country="COMPANY_NAME"
validCharactersOnly
v-model="newCustomer.primaryPhoneNumber"
:inputOptions="inputOptions">
</vue-tel-input>
The problem is when I set the value of my stub component, the data of the Vue component I'm testing is not being updated, when it should be rerendered thanks to v-model.
Thanks for your help.

Quasar Unknown custom element error in unit test

I have a simple Vue component that uses Quasar button
<template>
<div>
<span class="count">{{ count }}</span>
<q-btn #click="increment">Increment</q-btn>
</div>
</template>
<script>
export default {
name: 'TestComponent',
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count += 1;
},
},
};
</script>
I create a unit test for it
import { mount, createLocalVue } from '#vue/test-utils';
import { Quasar, QBtn } from 'quasar';
import TestComponent from '../TestComponent';
describe('TestComponent', () => {
let wrapper;
beforeEach(() => {
const localVue = createLocalVue();
localVue.use(Quasar, { components: { QBtn } });
wrapper = mount(TestComponent, { localVue });
});
it('renders the correct markup', () => {
expect(wrapper.html()).toContain('<span class="count">0</span>');
});
// it's also easy to check for the existence of elements
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true);
});
});
My problem:
If I run the test cases (it function) one by one at a time the test will pass. For example, remove the second it('has a button'...) then run the test. It'll pass. It's the same when removing the first it('renders the correct markup'...)
However, If I keep all test cases then run the test. The second test case will fail with an error
console.error node_modules/vue/dist/vue.common.dev.js:630
[Vue warn]: Unknown custom element: <q-btn> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <TestComponent>
<Root>
What am I doing wrong?
Try removing the before-each. I saw this problem too. Can't remember what exactly fixed it but this is how I have my describe block.
describe('Mount Quasar', () => {
const localVue = createLocalVue()
localVue.use(Quasar, { components })
const wrapper = shallowMount(Register, {
localVue,
stubs: ['router-link', 'router-view']
})
const vm = wrapper.vm
it('passes the sanity check and creates a wrapper', () => {
expect(wrapper.isVueInstance()).toBe(true)
})
})
You will need to import quasar into either webpack, babel, or jest.
In the jest.config.js file
Add
moduleNameMapper: {
quasar: "quasar-framework/dist/umd/quasar.mat.umd.min.js"
},