Test Vuex Action that Calls Getters and Mutations - vuex

What I would like to do is to test the following vuex action which calls a few mutations and getters. I can't tell if my issues cause of async/await issues or that I am not really calling/dispatching the action code.
Actions
export const actions = {
async updateTaxOnLine({dispatch, commit}, {lineId}) {
await commit('toggleTaxOnLine', {lineId});
await dispatch('updateTaxOnAllLines');
},
async updateTaxOnAllLines({getters, commit}) {
getters.receiptLines.forEach(l => {
if (l.add_tax) {
const tax = ((parseFloat(l.amount) / getters.taxLinesBaseAmount) * parseFloat(getters.receipt.tax)).toFixed(2);
console.log(`Setting tax to line ${l.id} calculating (${l.amount} / ${getters.taxLinesBaseAmount}) * ${getters.receipt.tax} = ${tax}`)
commit('setTaxOnLine', {lineId: l.id, tax: tax});
} else {
commit('setTaxOnLine', {lineId: l.id, tax: null});
}
});
},
}
Test
import Vuex from 'vuex';
import {createLocalVue} from "#vue/test-utils";
import {actions, getters, mutations, state} from './index'
let store;
beforeEach(() => {
createLocalVue().use(Vuex);
store = new Vuex.Store({
state,
getters,
mutations,
actions,
});
});
describe('store', () => {
it('set tax on a line sets the amount correctly', async () => {
store.replaceState({
receipt: {
"id": 28,
"created": "2021-01-14T22:41:53.023594Z",
"updated": "2021-06-06T01:52:11.092877Z",
"user": null,
"datetime": "2020-11-21T02:51:00Z",
"subtotal": "96.29",
"tax": "6.74",
"amount": "103.03",
"status": "CODING",
"error_message": null,
"lines": [
{
id: 355,
created: "2021-05-17T05:19:44.424661Z",
updated: "2021-06-05T21:10:04.856432Z",
entry_method: "AZURE_RECEIPT_AI",
quantity: "2.00",
unit_price: "2.99",
tax: null,
amount: "5.98",
personal: false,
add_tax: false, // THIS line is TAXED
},
]
}
});
await store.dispatch('toggleTaxOnLine', {lineId: 355});
expect(store.getters.receipt.lines[0].add_tax).toBeTruthy();
expect(store.getters.receipt.lines[0].tax).toEqual(6.74);
expect(store.getters.taxLinesBaseAmount).toEqual(5.98);
});
});
Mutations
export const mutations = {
toggleTaxOnLine: (state, {lineId}) => {
const foundLine = state.receipt.lines.find(l => l.id === lineId);
foundLine.add_tax = !foundLine.add_tax;
},
setTaxOnLine: (state, {lineId, tax}) => {
const foundLine = state.receipt.lines.find(l => l.id === lineId);
foundLine.tax = tax;
},
};
Getters
export const getters = {
receipt: state => state.receipt,
taxLinesBaseAmount: (state, getters) => getters.receiptLines.reduce((accum, l) => {
if (l.add_tax) {
return parseFloat(l.amount) + parseFloat(accum);
}
return accum;
}, 0),
};
The Error that I get is:
console.error
node_modules/vuex/dist/vuex.common.js:499
[vuex] unknown action type: toggleTaxOnLine

The problem was I didn't read the error message close enough. Basically I am calling dispatch on 'toggleTaxOnLine' when I needed to either update the dispatch call to 'updateTaxOnLine' or change the action to be toggleTaxOnLine I chose the former.

Related

Vuex is not mutating the state

I am trying to switch authenticated from false to true, a property in the state, It's not working.
My codes from store.js
state() {
return{
authenticated : false,
user : {}
}
},
getters : {
authenticated(state){
return state.authenticated
}
},
mutations : {
set_authenticated(state, value){
return state.authenticated = value
}
},
My updated code from login.vue (script)
data() {
return {
allerrors : [],
success : false,
data: {
email : "",
password : ""
}
}
},
methods : {
login: function() {
this.$store
.dispatch("login", this.data)
.then(response => {
this.allerrors = [],
this.success = true,
this.data = {}
alert(response.data)
})
.catch((error) => {
this.allerrors = error.response.data.error
this.success = false
alert(allerrors)
})
},
My updated action is :
async login({ commit }, data) {
await axios.post('login', data)
.then(response => {
commit('set_authenticated',true);
})
.catch((error) => {
this.allerrors = error.response.data.error
this.success = false
})
}
There are a few problems here:
First, if that is the full code for your store.js file, then you are missing the call to createStore() (for Vue 3) or new Vuex.Store() (for Vue 2)
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
Source
The second problem is that you shouldn't be committing mutations from your Single File Components. The typical flow is:
Components dispatch actions
Actions commit mutations
Mutations update state
You're trying to commit a mutation directly from the component.
You need to add an action to your store.js
async login({ commit }, userData) {
await axios.post('login', userData)
.then(response => {
commit('set_authenticated',true);
})
.catch((error) => {
this.allerrors = error.response.data.error
this.success = false
})
}
Mutation:
mutations : {
set_authenticated(state, value){
state.authenticated = value
}
},
Then your Login.vue would change to something like:
methods: {
login: function() {
this.$store
.dispatch("login", { userData })
.then(() => )) // whatever you want to do here.
.catch(err => console.error(err));
}
}
mutations shouldn't have a return statement. it should be like this
mutations : {
set_authenticated(state, value){
state.authenticated = value
}
},

How to dispatch store actions in namespaced modules ( NuxtJS)?

I have made lots of research and since keywords are always similar I cannot find a correct way of usage store modules in nuxtjs. I will shorten the codes to make it readable. In my nuxtjs application, I am trying to reach my vuex modules in my home.page but instead I get
pages/index.vue
TypeError
Cannot read property 'then' of undefined
created() {
this.$store.dispatch('articles/fetchIndexArticles')
.then(() => this.$store.dispatch('videolessons/fetchIndexVideolessons'))
.then(() => {
While creating modules first in the store folder i have created an index.js file:
import Vuex from "vuex";
import articles from "./modules/articles";
// ...
import videolessons from "./modules/videolessons";
const debug = process.env_NODE_ENV !== 'production';
export const store = new Vuex.Store({
modules: {
articles,
books,
members,
pages,
status,
user,
videolessons,
},
strict: debug,
plugins: [],
})
and basically my modules are similar to my articles module:
const getDefaultState = () => {
return {
indexArticles: [],
}
}
const state = getDefaultState()
const getters = {
indexArticles (state) {
return state.indexArticles
},
}
const mutations = {
fetchStart (state) {
state.loading = true
},
fetchEnd (state) {
state.loading = false
},
setIndexArticles (state, pArticles) {
state.indexArticles = pArticles
state.errors = {}
},
setError (state, errors) {
state.errors = errors
},
resetState (state) {
Object.assign(state, getDefaultState())
}
}
const actions = {
// ...
async fetchIndexArticles ({ commit }) {
try {
const response = await articlesService.fetchIndexArticles()
commit('fetchStart')
commit('setIndexArticles', response.data)
commit('fetchEnd')
return response
} catch (error) {
commit('setError', error)
this._vm.$q.loading.hide()
}
},
...
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
and in my index page:
<script>
import store from '../store/'
export default {
computed: {
indexarticles() {
return this.$store.getters['articles/indexArticles'];
}
},
created() {
this.$store.dispatch('articles/fetchIndexArticles')
.then(() => this.$store.dispatch('videolessons/fetchIndexVideolessons'))
...
.then(() => {
this.isLoading = false;
});
}
};
</script>
<template>...</template>
can you help to correct my store modules?
Thanks
ps:
videolessons.js
const getDefaultState = () => {
return {
indexvideolessons: [],
}
}
const state = getDefaultState()
const getters = {
indexVideolessons (state) {
return state.indexvideolessons
},
}
const mutations = {
setIndexVideolessons (state, pVideolessons) {
state.indexvideolessons = pVideolessons
state.errors = {}
},
}
const actions = {
async fetchIndexVideolessons ({ commit, dispatch }) {
try {
const response = await videolessonsService.fetchIndexVideolessons()
commit('setIndexVideolessons', response.data)
return response
} catch (error) {
commit('setError', error)
}
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

Vuejs with namespaced modules and unknow mutation type

i have created two store modules and I did the import into main store.
When I load my drawer view, I am getting the next error.
What am I doing wrong here
error:vuex.esm.js [vuex] unknown mutation type: SET_DRAWER
//Drawer.vue
.........
computed: {
...mapState("navstore", ["barColor", "barImage"]),
drawer: {
get() {
return this.$store.state.navstore.drawer;
},
set(val) {
this.$store.commit("SET_DRAWER", val);
},
},
computedItems() {
return null;
//this.items.map(this.mapItem);
},
profile() {
return {
avatar: true,
title: "Gestan",
};
},
},
methods: {},
.........
.........
//store/index.js
import Vue from "vue";
import Vuex from "vuex";
import { default as auth } from ".auth";
import { default as navstore } from "./navStore";
Vue.use(Vuex);
export default new Vuex.Store({
modules: { auth: auth, navstore: navstore },
});
//navstore.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const SET_BAR_IMAGE = "NAVSTORE/SET_BAR_IMAGE";
const SET_DRAWER = "NAVSTORE/SET_DRAWER";
export default {
namespaced: true,
state: {
barColor: "rgba(0, 0, 0, .8), rgba(0, 0, 0, .8)",
barImage:
"https://demos.creative-tim.com/material-dashboard/assets/img/sidebar-1.jpg",
drawer: null,
},
mutations: {
[SET_BAR_IMAGE](state, payload) {
state.barImage = payload;
},
[SET_DRAWER](state, payload) {
state.drawer = payload;
},
},
actions: {
actionSetDrawer({ commit }, payload) {
commit(SET_DRAWER, payload);
},
},
};
//auth.js
import Vue from "vue";
import Vuex from "vuex";
import * as storage from "#/store/storage";
import services from "#/services/api/AuthenticationService";
Vue.use(Vuex);
const SET_USER = "AUTH/SET_USER";
const SET_TOKEN = "AUTH/SET_TOKEN";
export default {
namespaced: true,
state: {
token: null,
user: null,
isUserLoggedIn: false,
},
mutations: {
[SET_TOKEN](state, token) {
state.token = token;
state.isUserLoggedIn = !!token;
},
[SET_USER](state, user) {
state.user = user;
},
},
getters: {
isAuthenticated: (state) => !!state.token,
// authStatus: state => state.status
},
actions: {
actionDoLogin({ dispatch }, payload) {
return services.login(payload).then((res) => {
dispatch("actionSetUser", res.data.user);
dispatch("actionSetToken", res.data.token);
});
},
};
You need to remove prefixes with slash from mutation names because your modules namespaced and you will always access this mutations outside indicating a module name like this moduleName/mutation.
For instance:
const SET_BAR_IMAGE = "NAVSTORE/SET_BAR_IMAGE";
const SET_DRAWER = "NAVSTORE/SET_DRAWER";
// should be
const SET_BAR_IMAGE = "SET_BAR_IMAGE";
const SET_DRAWER = "SET_DRAWER";
Because the SET_DRAWER mutation is inside a navspaces module you should call it indicating a module name:
this.$store.commit("navstore/SET_DRAWER", val);
Don't try to call actions inside actions like this:
actionDoLogin({ dispatch }, payload) {
return services.login(payload).then((res) => {
dispatch("actionSetUser", res.data.user);
dispatch("actionSetToken", res.data.token);
});
},
Use mutations in a desired combination:
actionDoLogin({ dispatch }, payload) {
return services.login(payload).then((res) => {
commit(SET_USER, res.data.user);
commit(SET_TOKEN, res.data.token);
});
},

Adding result of Axios call to State

This is a very basic question but I'm not getting anywhere...
Doing a simple shopping cart with Vue and Vuex. I had the products hard-coded in the state but now I'm trying to do an axios call and add that result to the state. I have the axios function and I'm trying to load the function and have the JSON result appended to the array all.
How do I put the result of loadItems() into the all array? Thank you
EDIT: I have updated with my entire vuex file.
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import * as types from './mutation-types'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
// axios call
const api = {
loadItems() {
// Init variables
var self = this
var app_id = "asdfasf";
var app_key = "asdfasfaf";
this.items = []
axios.get(
"https://api.airtable.com/v0/"+app_id+"/Products",
{
headers: { Authorization: "Bearer "+app_key }
}
).then(function(response){
self.items = response.data.records.map((item)=>{
return {
id: item.id,
...item.fields
}
})
}).catch(function(error){
console.log(error)
})
}
}
// initial state
const state = {
added: [],
all: []
}
// getters
const getters = {
allProducts: state => state.all, // would need action/mutation if data fetched async
getNumberOfProducts: state => (state.all) ? state.all.length : 0,
cartProducts: state => {
return state.added.map(({ id, quantity }) => {
const product = state.all.find(p => p.id === id)
return {
name: product.name,
price: product.price,
quantity
}
})
}
}
// actions
const actions = {
addToCart({ commit }, product){
commit(types.ADD_TO_CART, {
id: product.id
})
},
removeFromCart({ commit }, product){
commit(types.REMOVE_FROM_CART, {
id: product.id
})
}
}
// mutations
const mutations = {
[types.ADD_TO_CART] (state, { id }) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.push({
id,
quantity: 1
})
} else {
record.quantity++
}
},
[types.REMOVE_FROM_CART] (state, { id }) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.pop({
id,
quantity: 1
})
} else {
record.quantity--
}
},
}
// one store for entire application
export default new Vuex.Store({
state,
strict: debug,
getters,
actions,
mutations
})
This is a little approach to your goal:
You can access with state.yourStateNameVariable or better make a getter and commit to get/set value from that state.
Observations:
[types.ADD_TO_CART] is not a good name for commit, maybe addToCart and removeFromCart?
You dont need to save in items your response from axios, you can directly after resolve send to all state with state.all = yourdata or better, add mutation
setAllData({ state }, data){
state.all = data
}
I do not fixed all your code but here you are an approach:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import * as types from './mutation-types'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
state:{
added: [],
all: [
{
id: 'cc919e21-ae5b-5e1f-d023-c40ee669520c',
name: 'COBOL 101 vintage',
description: 'Learn COBOL with this vintage programming book',
price: 399
},
{
id: 'bcd755a6-9a19-94e1-0a5d-426c0303454f',
name: 'Sharp C2719 curved TV',
description: 'Watch TV like never before with the brand new curved screen technology',
price: 1995
},
{
id: '727026b7-7f2f-c5a0-ace9-cc227e686b8e',
name: 'Remmington X mechanical keyboard',
description: 'Excellent for gaming and typing, this Remmington X keyboard ' +
'features tactile, clicky switches for speed and accuracy',
price: 595
}
]
},
getters: {
allProducts: state => state.all, // would need action/mutation if data fetched async
getNumberOfProducts: state => (state.all) ? state.all.length : 0,
cartProducts: state => {
return state.added.map(({ id, quantity }) => {
const product = state.all.find(p => p.id === id)
return {
name: product.name,
price: product.price,
quantity
}
})
}
},
mutations: {
setAllData({ state }, data){
state.all = data
},
[types.ADD_TO_CART] ({ state }, id) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.push({
id,
quantity: 1
})
} else {
record.quantity++
}
},
[types.REMOVE_FROM_CART] ({ state }, id) {
const record = state.added.find(p => p.id === id)
if (!record) {
state.added.pop({
id,
quantity: 1
})
} else {
record.quantity--
}
}
},
actions:{
loadItems({getters, commit}, somethingYouReceive) {
// Init variables
var self = this
var app_id = "adsfasfasgag";
var app_key = "agasdgagasgasg";
this.items = []
axios.get(
"https://api.airtable.com/v0/"+app_id+"/Products",
{
headers: { Authorization: "Bearer "+app_key }
}
).then(function(response){
commit('setAllData',response.data.records.map((item)=>{
return {
id: item.id,
...item.fields
}
})
}).catch(function(error){
console.log(error)
})
},
addToCart({ commit }, product){
commit(types.ADD_TO_CART, {
id: product.id
})
},
removeFromCart({ commit }, product){
commit(types.REMOVE_FROM_CART, {
id: product.id
})
}
}
})

Vue.js - Vuexx : state value undefined

After user login authentication ( LoginPage component ) the currentUserId is set in the store, but trying to get it later in another component ( ShoppingLists ) gives an undefined value ... what's wrong with my flow ?
here is my store.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from '#/vuex/getters'
import actions from '#/vuex/actions'
import mutations from '#/vuex/mutations'
import vueAuthInstance from '../services/auth.js'
Vue.use(Vuex)
const state = {
shoppinglists: [],
isAuthenticated: vueAuthInstance.isAuthenticated(),
currentUserId: ''
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
Here are the console.log output with related pieces of code
// LoginPage component submit button fires the login action
methods: _.extend({}, mapActions(['login']), {
clearErrorMessage () {
this.hasError = false
},
submit () {
return this.login({user: { email: this.email, password: this.password }})
.then((logged) => {
if (logged) {
this.$router.push('shoppinglists')
} else {
this.hasError = true
}
})
}
}),
action.js
login: ({ commit }, payload) => {
payload = payload || {}
return vueAuthInstance.login(payload.user, payload.requestOptions)
.then((response) => {
// check response user or empty
if (JSON.stringify(response.data) !== '{}') {
commit(IS_AUTHENTICATED, { isAuthenticated: true })
commit(CURRENT_USER_ID, { currentUserId: response.data.id })
return true
} else {
commit(IS_AUTHENTICATED, { isAuthenticated: false })
commit(CURRENT_USER_ID, { currentUserId: '' })
return false
}
})
},
console.log
mutations.js?d9b0:23
state isAuthenticated: true
mutations.js?d9b0:27
committed state currentUserId: 1
At this point the store should be updated ....
// then the LoginPage component push the ShoppingListsPage
when mounted it shoudl populates the shoppinglists
methods: _.extend({}, mapActions(['populateShoppingLists', 'createShoppingList']), {
addShoppingList () {
let list = { title: 'New Shopping List', items: [] }
this.createShoppingList(list)
}
}),
store,
mounted: function () {
this.$nextTick(function () {
console.log('GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER')
this.populateShoppingLists()
})
}
console.log
ShoppingListsPage.vue?88a1:52
GOING TO POPULATE STORE SHOPPINGLISTS FOR CURRENT USER
actions.js?a7ea:9
TRYING TO GET currentUserId with GETTERS
populateShoppingLists: ({ commit }) => {
console.log('TRYING TO GET currentUserId with GETTERS')
const currentUserId = getters.getCurrentUserId({ commit })
console.log('ACTIONS: populateShoppingLists for user: ', currentUserId)
return api.fetchShoppingLists(currentUserId)
.then(response => {
commit(POPULATE_SHOPPING_LISTS, response.data)
return response
})
.catch((error) => {
throw error
})
},
console.log
getters.js?d717:9
GETTERS: currentUserId: undefined
Getters returning an undefined value from the store
getCurrentUserId: (state) => {
console.log('GETTERS: currentUserId: ', state.currentUserId)
return state.currentUserId
},
UPDATE
mutations.js
import * as types from './mutation_types'
import getters from './getters'
import _ from 'underscore'
export default {
[types.POPULATE_SHOPPING_LISTS] (state, lists) {
state.shoppinglists = lists
},
[types.IS_AUTHENTICATED] (state, payload) {
console.log('committed state isAuthenticated: ', payload.isAuthenticated)
state.isAuthenticated = payload.isAuthenticated
},
[types.CURRENT_USER_ID] (state, payload) {
console.log('committed state currentUserId: ', payload.currentUserId)
state.currentUserId = payload.currentUserId
}
}
mutation_types
export const POPULATE_SHOPPING_LISTS = 'POPULATE_SHOPPING_LISTS'
export const IS_AUTHENTICATED = 'IS_AUTHENTICATED'
export const CURRENT_USER_ID = 'CURRENT_USER_ID'
I solved the issue , modifying the action populateShoppingLists
Need to pass the state as a parameter with the commit , so I can use the getters inside my action
populateShoppingLists: ({ commit, state }) => {
let currentUserId = getters.currentUserId(state)
console.log('currentUserId: ', currentUserId). // => userId: 1 Ok
return api.fetchShoppingLists(currentUserId)