Test vue watcher in jasmine which triggers a fetch method - vue.js

I've implemented a small autocomplete which fetches information based upon the search input. In the frontend everythign works fine but when I try to test my watcher it always states that the items array is empty. I guess something in my async/await structure in the test is wrong but I cannot figure out what it is.
<template>
<v-autocomplete
v-model="inputText"
:items="items"
:search-input.sync="search"
></v-autocomplete>
</template>
<script>
export default {
name: 'Autocomplete',
data() {
return {
items: [],
inputText: '',
search: '',
};
},
watch: {
search(val) {
this.getDataFromApi(val);
},
},
methods: {
getDataFromApi(val) {
this.loading = true;
return fetch(
'https://jsonplaceholder.typicode.com/todos/' +
val
)
.then(response => response.json())
.then(data => {
this.items.push(data);
})
.finally(() => {
this.loading = false;
});
},
},
};
</script>
it('validates the Autocomplete properties', async function(done) {
// GIVEN an instantiated Search
const wrapper = shallowMount(Autocomplete, {
localVue: this.localVue,
propsData: mockProps,
});
await wrapper.setData({ search: 'asdf' });
expect(wrapper.vm._data.items).toEqual([
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
]);
});

Related

Problems with vuex: Uncaught (in promise) TypeError: _ctx.getProduct.image_url is undefined

I'm trying to load a product's data, passing props through the Vue Router, this is my code:
...store/modules/products_manager.js
import axios from 'axios';
const BASE_URL = "http://127.0.0.1:3000/"
const state = {
product: [],
products: [],
category: "",
}
const getters = {
getProducts(state) {
return state.products;
},
getProduct(state) {
console.log("GET")
console.log(state.product)
return state.product;
},
getCategory(state) {
return state.category;
},
}
const actions = {
getAllProducts({ commit }, payload) {
const config = {
params: {
title: payload['name']
}
}
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}products/`, config)
.then((response) => {
commit("setProducts", response);
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
getProductById({ commit }, payload) {
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}products/${payload}`)
.then((response) => {
commit("setProduct", response);
console.log("PRODUCT")
console.log(response.data)
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
getCategoryById({ commit }, payload) {
new Promise((resolve, reject) => {
axios
.get(`${BASE_URL}categories/${payload}`)
.then((response) => {
commit("setCategory", response);
resolve(response.data);
})
.catch((error) => {
reject(error);
});
});
},
}
const mutations = {
setProducts(state, data) {
state.products = data.data;
},
setProduct(state, data) {
console.log("SET")
console.log(data.data)
state.product = data.data;
},
setCategory(state, data) {
state.category = data.data;
},
}
export default {
state,
getters,
actions,
mutations,
}
../components/ProductPage.vue
<template lang="">
<div>
<p v-if="(getProduct != null || getProduct != undefined)" >{{ getProduct['image_url'][0] }}</p>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'Product',
props: ["id", "category_id"],
computed: {
...mapGetters(["getProduct", "getCategory"]),
},
mounted() {
console.log(this.id)
console.log(this.category_id)
this.$store.dispatch("getProductById", this.id)
this.$store.dispatch("getCategoryById", this.category_id)
}
}
</script>
But I'm having some problems, I'm starting with vuex and I'm not understanding many things yet.
Error Printscreen
I did some tests, I used functions like created, updated, etc. With some of them, the information was even displayed on the screen, but it still generated the same errors. I believe it must be some error in the vue data flow, but I still don't understand how to solve it.
Sorry my bad english ;)
Solution:
<div>
<p v-if="(getProduct['image_url'] != null || getProduct['image_url'] != undefined)" >{{ getProduct['image_url'][0] }}</p>
</div>
Thank's #yoduh

How to trigger confetti with if condition

I have a confetti that I am using it with this package: https://www.npmjs.com/package/vue-confetti
Here is my template:
<template>
<div>{{ confetti }}</div>
</template>
there is an if condition in the button which I need to render that confetti in the same condition:
But I want to use it at the top of my component because I want to use it more efficiently on the full screen.
data() {
return {
array: [],
confetti: null,
confettiEnabled: false,
};
},
methods: {
getArray() {
this.isLoading = true;
axios
.get(`api`, {
params: {},
})
.then((response) => {
this.array = response.data.data;
this.arrayLength = this.array.length;
})
.finally(() => {
this.isLoading = false;
});
this.confettiEnabled = true;
console.log(this.confettiEnabled);
},
},
mounted() {
this.getArray();
},
and I created watcher for the component:
watch: {
confetti: {
handler() {
if (this.documents.length == 0 && this.confettiEnabled) {
this.confettiEnabled = true;
this.$confetti.start();
setTimeout(() => {
this.$confetti.stop();
}, 3000);
}
},
immediate: true, // This ensures the watcher is triggered upon creation
},
},
But this logic doesnt work even and my confetti never triggers.

Vuejs Vuex sometimes initial state not working Error: [Vue warn]: Error in render: "TypeError: Cannot read property 'Any_Variable' of undefined"

Other pages are working fine. Only facing issue with this file. May be I am coding wrong.
Store file is included in app.js file as other pages are working I have not included it.
Here Sometimes I get undefined MDU_Number. Sometimes it work fine. I am new to vue js.
Image of error that I am receving:
This is my vue template
<div class="card-body">
<div class="form-group row">
<label class="col-sm-4 col-form-label">MDU Number</label>
<div class="col">
<input
name="MDU_Number"
:value="mduprofile.MDU_Number"
#input="updateMDUNumber"
type="text"
class="form-control"
placeholder="Enter MDU Number Ex:GJXXCHXXXX"
required
/>
</div>
</div>
</div>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
};
},
created() {
this.fetchForMDU();
},
destroyed() {
this.resetState();
},
computed: {
...mapGetters("MDUSingle", [
"loading",
"country",
"area",
"product",
"mduprofile",
]),
},
methods: {
...mapActions("MDUSingle", [
"resetState",
"fetchForMDU",
"storeMDU",
"setMDUNumber",
]),
submitForm() {
this.storeMDU()
.then(() => {
this.resetState();
this.$eventHub.$emit(
"create-success",
"Created",
"MDU created successfully"
);
})
.catch((error) => {
console.log(error);
});
},
updateMDUNumber(e) {
this.setMDUNumber(e.target.value);
},
},
};
</script>
This is store file name single.js and I have included it in app.js file
MDU_Number should go for null value but it goes for undefined. So I think it is not initialized properly. There are many other variables but for simplicity purpose I have included only one.
What can be the issue?
function initialState() {
return {
mduprofile: {
MDU_Number: null,
},
country: [],
area: [],
product: [],
loading: false
};
}
const getters = {
country: state => state.country,
area: state => state.area,
product: state => state.product,
loading: state => state.loading,
mduprofile: state => state.mduprofile
}
const actions = {
fetchForMDU({ commit }) {
return new Promise((resolve, reject) => {
axios.get('/get/detail/for/mdu')
.then((response) => {
let detail = response.data;
commit('setCountryAll', detail.country);
commit('setStateAll', detail.state);
commit('setProductAll', detail.product);
}).catch(error => {
reject(error);
}).finally(() => {
resolve();
});
});
},
storeMDU({ commit, state, dispatch }) {
commit('setLoading', true);
dispatch('Alert/resetState', null, { root: true });
return new Promise((resolve, reject) => {
let params = _.cloneDeep(state.mduprofile);
axios.post('/save-mdu-profile', params)
.then((response) => {
resolve();
})
.catch(error => {
commit('setLoading', false);
let message = error.response.data.message || error.message;
let errors = error.response.data.errors;
dispatch('Alert/setAlert',
{ message: message, errors: errors, color: danger },
{ root: true });
reject(error);
}).finally(() => {
commit('setLoading', false);
});
});
},
fetchData({ commit }, value) {
axios.get('/mdu/profile/' + value)
.then((response) => {
commit('setAll', response.data.mdu);
}).catch(error => {
}).finally(() => {
});
},
updateMDU({ commit, state, dispatch }) {
commit('setLoading', true);
dispatch('Alert/setAlert', null, { root: true });
return new Promise((resolve, reject) => {
let params = _.cloneDeep(state.mduprofile);
axios.put('/update-mdu-profile/' + params.MDU_Id, params)
.then((response) => {
resolve();
}).catch(error => {
let message = error.response.data.message || error.message;
let errors = error.response.data.errors;
dispatch('Alert/setAlert',
{ message: message, errors: errors, color: danger },
{ root: true });
commit('setLoading', false);
reject(error);
}).finally(() => {
commit('setLoading', false);
});
});
},
resetState({ commit }) {
commit('resetState');
},
setMDUNumber({ commit }, value) {
commit('setMDUNumber', value);
}
}
const mutations = {
resetState(state) {
state = Object.assign(state, initialState());
},
setLoading(state, loading) {
state.loading = loading;
},
setCountryAll(state, items) {
state.country = items
},
setStateAll(state, items) {
state.area = items;
},
setProductAll(state, items) {
state.product = items;
},
setAll(state, items) {
state.mduprofile = items;
},
setMDUNumber(state, value) {
state.mduprofile.MDU_Number = value;
},
setCountry(state, value) {
state.mduprofile.Country = value;
},
setState(state, value) {
state.mduprofile.State = value;
},
setProduct(state, value) {
state.mduprofile.Product = value;
}
}
export default {
namespaced: true,
state: initialState,
getters,
actions,
mutations
}
Try checking somewhere where you change this values, if you don't catch error properly you may encounter empty states.

Making pagination with laravel paginate() and vuex,

I am really having a hard time to solve this problem. Trying to make pagination by using vuex. But I can't update actions when I change the argument value.
For example to go to the next page, I tried a simple way
in component: home
<button #click="nextPage()">{{currentPage}}</button>
I send the argument to actions.
mounted(){
this.$store.dispatch('bridalApi', {currentPage})
},
data(){
return {
currentPage: 1,
};
},
methods: {
nextPage(){
this.currentPage++
}
},
in store.js
I take the argument that I commited.
actions: {
bridalApi({commit}, currentPage){
axios.get("api/bridal?page=" + currentPage)
.then(response => {
commit("setBridals", response.data);
})
.catch(e => {
console.log(e);
})
},
}
it's clearly I can't update the argument inside actions. Because when I click the button, it doesn't go to next page. I mean currentPage inside actions doesn't updated. This was the first way. So, I tried different approach to solve this problem which is like below.
in component: home
<button #click="nextPage()">{{pager}}</button>
I set/get the currentPage, and change the state.
methods: {
nextPage(){
this.pager++
}
},
computed: {
...mapGetters([
"getBridals",
]),
pager: {
set(val){
this.$store.commit("setPagination", val);
},
get(){
return this.$store.state.bridal.pagination.currentPage;
}
},
bridals() {
return this.getBridals;
},
},
in Store.js
state: {
bridals: [],
pagination: {
currentPage: 1,
},
},
mutations: {
setBridals(state, bridal){
state.bridals = bridal;
},
setPagination(state, pager){
state.pagination.currentPage = pager;
},
},
getters: {
getBridals(state){
return state.bridals
},
},
actions: {
bridalApi({commit,state}){
console.log(state.pagination.currentPage)
axios.get("api/bridal?page=" + state.pagination.currentPage)
.then(response => {
commit("setBridals", response.data);
})
.catch(e => {
console.log(e);
})
},
}
But this way is not working either. And I am very much out of ideas. How can I update the actions? What is the right way to use vuex for pagination?...
I am not sure is it's the right way to this. But solved it. Used the first way I mentioned in the question and update home component like below.
data(){
return {
currentPage: 1,
};
},
watch: {
currentPage() {
this.$store.dispatch("bridalApi", this.currentPage);
console.log("ok")
}
},
You can use a mutation for this like
state:{
data:[]
}
mutations:{
SET_DATA:(state , data) => {
return state.data = data
}
}
actions: {
dataApi({commit}, currentPage){
console.log(currentPage)
axios.get("http://website.com/api/endpoint?page="+currentPage)
.then(response => {
commit('SET_DATA' , response.data)
})
.catch(e => {
console.log(e);
})
}
}
I would recommend using LaravelVuePagination package for this.
That way you can have something like:
<pagination :data="bridals" #pagination-change-page="getBridals"></pagination>
export default {
name: 'BridlaList',
mounted() {
this.getBridals();
},
methods: {
getBridals(page = 1){
this.$store.dispatch('getBridals',{
page: page
});
},
}

Computed property "main_image" was assigned to but it has no setter

How can I fix this error "Computed property "main_image" was assigned to but it has no setter"?
I'm trying to switch main_image every 5s (random). This is my code, check created method and setInterval.
<template>
<div class="main-image">
<img v-bind:src="main_image">
</div>
<div class="image-list>
<div v-for="img in images" class="item"><img src="img.image"></div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Item',
data () {
return {
item: [],
images: [],
}
},
methods: {
fetchImages() {
axios.get(`/api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/`)
.then(response => {
this.images = response.data
})
.catch(e => {
this.images = []
this.errors.push(e)
})
},
},
computed: {
main_image() {
if (typeof this.item[this.$route.params.attribute] !== 'undefined') {
return this.item[this.$route.params.attribute].image_url
}
},
},
watch: {
'$route' (to, from) {
this.fetchImages()
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`)
.then(response => {
this.item = response.data
})
.catch(e => {
this.errors.push(e)
})
this.fetchImages();
self = this
setInterval(function(){
self.main_image = self.images[Math.floor(Math.random()*self.images.length)].image;
}, 5000);
},
}
</script>
Looks like you want the following to happen...
main_image is initially null / undefined
After the request to /api/item/${this.$route.params.id}/ completes, it should be this.item[this.$route.params.attribute].image_url (if it exists)
After the request to /api/item/${this.$route.params.id}/${this.$route.params.attribute}/images/ completes, it should randomly pick one of the response images every 5 seconds.
I'd forget about using a computed property as that is clearly not what you want. Instead, try this
data() {
return {
item: [],
images: [],
main_image: '',
intervalId: null
}
},
methods: {
fetchImages() {
return axios.get(...)...
}
},
created () {
axios.get(`/api/item/${this.$route.params.id}/`).then(res => {
this.item = res.data
this.main_image = this.item[this.$route.params.attribute] && this.item[this.$route.params.attribute].image_url
this.fetchImages().then(() => {
this.intervalId = setInterval(() => {
this.main_image = this.images[Math.floor(Math.random()*this.images.length)].image;
})
})
}).catch(...)
},
beforeDestroy () {
clearInterval(this.intervalId) // very important
}
You have to add setter and getter for your computed proterty.
computed: {
main_image: {
get() {
return typeof this.item[this.$route.params.attribute] !== 'undefined' && this.item[this.$route.params.attribute].image_url
},
set(newValue) {
return newValue;
},
},
},