vue-test-utils how to use trigger's options - vuejs2

I want to test el-button if it can change to the correct view by $router,but I am confused weather the trigger supportted cause i find this in its document
The trigger method takes an optional options object. The properties in
the options object are added to the Event.
Note that target cannot be added in the options object.
const wrapper = mount(MyButton)
wrapper.trigger('click', {
button: 0
})
but failed and i get this info
TypeError: Cannot set property type of [object Event] which has only a
getter
65 | // expect(mockFn).toBeCalled
66 | // expect(mockFn).toBeCalledTimes(1)
67 | wrapper.find(ElementUI.Button).trigger('click', {
| ^
68 | id: 1,
69 | type: 'view'
70 | })
vue file
<el-button
plain
type="primary"
#click="changeView(-1, 'edit')">
newPicture
</el-button>
js
changeView(id, type) {
if (type === 'view') {
this.$router.push({ path: './detail', query: { id, type }})
} else if (type === 'edit') {
this.$router.push({ path: './edit', query: { id, type }})
}
},
And I want to write a test file for this button
...
it('add button click', () => {
const mockFn = jest.fn()
wrapper.setMethods({
changeView: mockFn
})
wrapper.find(ElementUI.Button).trigger('click', {
id: 1,
type: 'view'
})
wrapper.update()
expect(mockFn).toBeCalled
console.log(wrapper.vm.$route.path)
})
...
How can I fix this?

the options your passing are js $event properties. here's a list of them: https://www.w3schools.com/jsref/obj_event.asp
when you trigger that click event, the changeView will be called, with the parameters you passed to it(-1 and 'edit'). they are not event properties and you dont pass them as options. so your test whould look like:
it('add button click', () => {
const mockFn = jest.fn()
wrapper.setMethods({
changeView: mockFn
})
wrapper.find(ElementUI.Button).trigger('click');
wrapper.update();
expect(mockFn).toBeCalledWith(-1,'edit');
})

Related

Why i am getting a warning about of string type if i am passing a string?

i want to pass a String to my child component like this, but previously i want to print it
this is my parent component
{{left.leftA}} // here show me 8
<CustomCard
:left="left.leftA"
export default {
name: 'Parent',
data() {},
setup() {
onMounted(async () => {
const answer = await getData(name)
left.value = answer.response //{leftA:'A', leftB:'B'...}
})
and in my child component i have this declaration
export default {
name: 'CustomCard',
props: {
left: {
type: String,
required: true,
},
i am getting this warning:
[Vue warn]: Invalid prop: type check failed for prop "left". Expected String with
value "undefined", got Undefined
Does it have something to do with how I am loading the data? is it ok to use onMounted?
This is happening because the initial value for value is null. So, on initial render it throws the warning, but upon another render it has the correct prop type (a string) and renders correctly.
You have 3 options. Allow '' as an option on the prop or don’t render the component until you have the correct data or make use of computed Property.
Option-1
{{left.leftA}} // here show me 8
<CustomCard
:left="left.leftA ? left.leftA : ''"
Option-2
{{left.leftA}} // here show me 8
<CustomCard v-if="loaded"
:left="left.leftA"
and in onMounted(}
onMounted(async () => {
const answer = await getData(name)
left.value = answer.response //{leftA:'A', leftB:'B'...}
// Set the loaded flag as true here. Also make sure its set as false inside the setup()
})
Option-3
{{left.leftA}} // here show me 8
<CustomCard
:left="sendVal"
In computed....
computed: {
sendVal() {
if(left && left.left1) return left.left1;
return '';
}
}

How can I put a data inside the <template> in a component with Vue

I am trying to do a pagination but I can not put the dynamic total I am doing like this:
<v-pagination v-model="currentPage"
:page-count="total"
:classes="bootstrapPaginationClasses"
:labels="paginationAnchorTexts"
></v-pagination>
How you can see the total os in the :page-count, it is a dynamic total because I am getting data from database, my vue code is this one:
<script>
import vPagination from 'vue-plain-pagination';
export default {
created() {
this.getPosts();
},
methods: {
getPosts() {
fetch('/api/bank')
.then(response => response.json() )
.then(json => {
this.posts = json.data.data;
this.total = json.data.last_page;
this.current_page = json.data.current_page;
});
}
},
components: { vPagination },
data: function() {
return {
postsSelected: "",
posts: [],
currentPage: 1,
total: this.total,
bootstrapPaginationClasses: {
ul: 'pagination',
li: 'page-item',
liActive: 'active',
liDisable: 'disabled',
button: 'page-link'
},
paginationAnchorTexts: {
first: 'Primera',
prev: '«',
next: '»',
last: 'Última'
}
}
}
}
</script>
How you can see I am using fetch to get the data from database and then I am split it in different information like total and the I am using this information inside the data: function() {}.
How you can tell total it's like this: total: this.total because I want to get the total number but when I do that I am getting this error:
[Vue warn]: Invalid prop: type check failed for prop "pageCount". Expected Number with value NaN, got Undefined
and I think that it is because:
total: this.total in the data function() {} is bad or:
how can I put the dynamic variable total inside the
How could I fix it?
Thanks!
If you want to know the data retrieved from the API, you can console log the data returned like this:
getPosts() {
fetch('/api/bank')
.then(response => response.json() )
.then(json => {
console.log(json.data)
this.posts = json.data.data;
this.total = json.data.last_page;
this.current_page = json.data.current_page;
});
}
Also, you should not have data attribute and props attribute with the same name! So change the total data attribute to another name and initialize it with a value of 0 instead.
In fact, you don't need to care about passing the Prop total at all as your method getPosts is not dependent on the Prop! So you may just have total: 0 in data and that should fix your issues

Nuxt.js : combine transition(to, from) and enter / leave events

So I have this code, it decides what to do on a transition between two pages and it works well :
export default {
transition: {
mode: 'out-in',
css: false,
beforeEnter (el) {
console.log('set transition');
},
enter (el, done) {
console.log('enter transition');
done();
},
leave (el, done) {
console.log('leave transition');
done();
},
}
}
Now I would like to specify what to do depending on what the next page is. So I've to use the transition(to, from) method according to the documentation. But unfortunately I can't mix this function with parameters like mode: 'out-in' and I can't manage to call the leave(el, done) function with the to, from parameters.
Does anyone knows how to combine this ? Thanks.
Maybe you found the solution, but I managed to combine transition with routes (to, from) and the object transition.
Let me explain :
If you look a the TypeScript declaration for vue, especially the ComponentOptions interface (node_modules/#nuxt/types/app/vue.d.ts)
Here's what we have :
declare module 'vue/types/options' {
// eslint-disable-next-line no-unused-vars,#typescript-eslint/no-unused-vars
interface ComponentOptions<V extends Vue> {
// eslint-disable-next-line #typescript-eslint/ban-types
asyncData?(ctx: Context): Promise<object | void> | object | void
fetch?(ctx: Context): Promise<void> | void
fetchKey?: string | ((getKey: (id: string) => number) => string)
fetchDelay?: number
fetchOnServer?: boolean | (() => boolean)
head?: MetaInfo | (() => MetaInfo)
key?: string | ((to: Route) => string)
layout?: string | ((ctx: Context) => string)
loading?: boolean
middleware?: Middleware | Middleware[]
scrollToTop?: boolean
transition?: string | Transition | ((to: Route, from: Route | undefined) => string | Transition)
validate?(ctx: Context): Promise<boolean> | boolean
watchQuery?: boolean | string[] | ((newQuery: Route['query'], oldQuery: Route['query']) => boolean)
meta?: { [key: string]: any }
}
}
The transition attribute is defined as follow :
transition?: string | Transition | ((to: Route, from: Route | undefined) => string | Transition)
So, by using the function approach, you must give either a string or a Transition object.
Example :
transition(to, from) {
if(!from) {
// returns a string
return 'my-custom-css-animation';
}
// returns an object Transition
return {
name: 'custom',
appear:false,
css: false,
beforeLeave() {
// before leave hook
},
leave(el, done) {
// leave hook
},
beforeEnter(el) {
// before enter hook
},
enter(el, done) {
// Enter hook
}
}
}
You should use pageTransition instead.
And did you try with a function?
export default {
pageTransition (to, from) {
if (!from) { return 'slide-left' }
return +to.query.page < +from.query.page ? 'slide-right' : 'slide-left'
}
}
Doc here: https://fr.nuxtjs.org/api/pages-transition/#fonction

Get id of item clicked and use it for creating dynamic url in vuejs

I have a vue bootstrap table displaying, in each row, few properties of objects of an array (got through an api call with axios).
Every row has a button that should redirect me to a detail page, with more properties of that object, plus a map.
I was thinking to make a function to get the property id of the object contained in the clicked row, but I'm not sure on how to do it. I need the id to use it in the last part of the api call.
The store is structured so that I have a module for the user and another one for these objects (activities). In these modules I deal with state, actions and mutations. A separate file handles the getters. As these activities will be modified, I need to save their state too.
I will also need to be able to easily access all the properties of the single object (not only the ones shown in the table row) from other components.
I'm getting very confused.
Here the code:
Table with all the activities:
<b-table
responsive
:fields="fields"
:items="activity"
>
<template
slot="actions"
>
<b-button
v-b-tooltip.hover
title="Mostra dettagli"
variant="info"
class="px-3"
#click="goToActivityDetail"
>
<span class="svg-container">
<svg-icon icon-class="search"/>
</span>
</b-button>
</template>
</b-table>
In the script:
export default {
name: 'AllActivities',
data() {
return {
fields: [
{ key: 'activity.activityName', label: 'Activity', _showDetails: true},
{ key: 'related_activity', label: 'Related activity', _showDetails: true},
{ key: 'start', label: 'Start', _showDetails: true },
{ key: 'end', label: 'End', _showDetails: true },
{ key: 'travel_mode', label: 'Travel mode', _showDetails: true },
{ key: 'actions', label: '' }
],
activity: [],
methods: {
getIdActivity(){
**?? how to get it ??**
},
goToActivityDetail() {
this.$router.push({
name: 'activityDetail'
})
}
}
goToActivityDetail()
obviously does not work, in the console:
- [vue-router] missing param for named route "activityDetail": Expected "activityId" to be defined
- [vue-router] missing param for redirect route with path "/see-all-activities/:activityId": Expected "activityId" to be defined)
In the getters file I have:
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token
}
export default getters
So here I will need to have something like:
activityId: state => state.activity.activityId
Which is coming from activity.js, which is:
import {
getActivityId
} from '#/components/AllActivities'
const state = {
activityId: getActivityId()
}
const mutations = {
SET_ACTIVITY_ID: (state, activityId) => {
state.activityId = activityId
}
}
const actions = {
setActivityId({
commit
}) {
return new Promise(resolve => {
commit('SET_ACTIVITY_ID', '')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
IF this is right, what is left is the function to get the id of the object contained in the table row clicked.
Also, how to write that activity id in the api call (axios)?
Now I have:
export function getSingleActivity() {
return request({
url: 'http://localhost:8000/api/user_activity/:activityId',
method: 'get'
})
}
But I am not sure if that's correct.
Also, how to access the other properties (to be displayed in the detailActivity page)?
This will be made of a list of some properties (probably a stacked table component) and a map component, so I will need to access the properties in both these components.
I hope I've been clear enough,
thank you.
It was dead simple. I post how to solve it in case someone else get stuck on this too.
I added a slot scope to the template that contains the button:
<template
slot="actions"
slot-scope="data"
>
Then I added the single activity (following the vue bootstrap markup data.item) as parameter to the button click
#click="goToDetailActivity(data.item)"
And the function called by the click became:
goToDetailActivity(activity) {
this.$router.push({
name: 'DettaglioAttivita',
params: { activityId: activity.id }
})
}
That's it.
Worth mentioning is you're using vuex. If I understand correctly you want to get the property read from vuex?
To read a property from vuex you can eather use this.$store.getters.activity
Or use mapGetter.
Read the following page https://vuex.vuejs.org/guide/getters.html
Also you have to set the param when you do a router.push
router.push({ name: 'activity', params: { id: activityId } })

I have event duplication after action was moved in store object

In my laravel 5.8 / vue 2.5.17 / vuex^3.1.0 I have a problem that with dialog opened I have event duplication.
I have an event for item deletion :
In my vue file:
...
mounted() {
bus.$on('dialog_confirmed', (paramsArray) => {
if (paramsArray.key == this.deleteFromUserListsKey(paramsArray.user_list_id)) {
this.runDeleteFromUserLists(paramsArray.user_list_id, paramsArray.index);
}
})
bus.$on('onUserListDeleteSuccess', (response) => {
this.is_page_updating = false
this.showPopupMessage("User lists", 'User\'s list was successfully deleted!', 'success');
})
bus.$on('onUserListDeleteFailure', (error) => {
this.$setLaravelValidationErrorsFromResponse(error.message);
this.is_page_updating = false
this.showRunTimeError(error, this);
this.showPopupMessage("User lists", 'Error adding user\'s list !', 'error');
})
}, // mounted() {
methods: {
confirmDeleteUserList(user_list_id, user_list_title, index) {
this.confirmMsg("Do you want to exclude '" + user_list_title + "' user list ?", {
key: this.deleteFromUserListsKey(user_list_id), user_list_id: user_list_id, index: index
}, 'Confirm', bus);
}, //confirmDeleteUserList(id, user_list_title, index) {
deleteFromUserListsKey(user_list_id) {
return 'user_list__remove_' + user_list_id;
},
runDeleteFromUserLists(user_list_id, index) {
this.$store.dispatch('userListDelete', { logged_user_id : this.currentLoggedUser.id, user_list_id : user_list_id } );
}, // runDeleteFromUserLists() {
and in resources/js/store.js :
state : {
...
userLists: [],
...
actions : {
userListDelete(context, paramsArray ) {
axios({
method: ( 'delete' ),
url: this.getters.apiUrl + '/personal/user-lists/' + paramsArray.user_list_id,
}).then((response) => {
let L = this.getters.userLists.length
for (var I = 0; I < L; I++) {
if (response.data.id == this.getters.userLists[I].id) {
this.getters.userLists.splice(this.getters.userLists.indexOf(this.getters.userLists[I]), 1)
context.commit('refreshUserLists', this.getters.userLists);
break;
}
}
bus.$emit( 'onUserListDeleteSuccess', response );
}).catch((error) => {
bus.$emit('onUserListDeleteFailure', error);
});
}, // userListDelete(context, paramsArray ) {
confirmMsg (based on https://github.com/euvl/vue-js-modal )is defined in my mixing :
confirmMsg: function (question, paramsArray, title, bus) {
this.$modal.show('dialog', {
title: title,
text: question,
buttons: [
{
title: 'Yes',
default: true, // Will be triggered by default if 'Enter' pressed.
handler: () => {
bus.$emit('dialog_confirmed', paramsArray);
this.$modal.hide('dialog')
}
},
{
title: '', // Button title
handler: () => {
} // Button click handler
},
{
title: 'Cancel'
}
]
})
},
it worked ok, until I moved userListDelete method from my vue file into store.js.
As a result on 1st event item is deleted ok, the the second item raise error that item was not found and I do not know event is doubled...
How to fix it ?
UPDATED BLOCK :
I still search for valid decision :
I uploaded live demo at :
http://178.128.145.48/login
demo#demo.com wdemo
http://178.128.145.48/websites-blogs will be opened.
Please, try to go to “User's lists” by link at top left menu https://prnt.sc/nq4qiy
and back several times. When on “User's lists” page I try to delete 1 user list it is deleted, but I got several messages
and url in “network” section of my browser : https://imgur.com/a/4ubFB0g
Looks like events are duplicated. And looks like that is move between pages number of guplications is raised.
Why and how to fix it ?
I use #click.prevent in triggering the event to show confirm delete message.
There is “ Add Demo Data” to add more demo rows.
Thanks!
Well, it is quite obvious.
Take a closer look at the Vue component lifecycle diagram.
Your component is mounted each time you enter a route.
So, bus.$on inside your mounted block executed each time you enter this route.
I suggest you move bus event handlers to some other location. For example app.js/ App.vue mounted hook or directly into the store. Since all you do inside handler is calling store actions.