I'm switching from Vuex to Pinia in Vue 3 and my unit tests are now failing. I can't seem to be able to create custom mock actions - vue.js

Here's a simple example on the forgot password reset page of my app, I would want to bypass the server side and just have the password reset to succeed on click so I would write a test and use the custom test store like so:
const customStore = {
state() {
return {
Authentication: {
passwordResetSuccess: false,
},
};
},
mutations: {
SET_RESET_PASSWORD_SUCCESS(state) {
state.Authentication.passwordResetSuccess = true;
},
},
actions: {
forgotPasswordResetPassword() {
this.commit('SET_RESET_PASSWORD_SUCCESS');
},
},
};
Then I could include the custom store in my beforeEach() and it worked great. I've tried everything I can think of to get this to work with pinia, but it doesn't seem to work.
I'm using jest along with vue/test-utils.
I basically tried just creating the test pinia store, but I can't figure out how to get the component to use the custom test store.
const useCustomStore = defineStore('AuthenticationStore', {
state: () => ({
passwordResetSuccess: false,
}),
actions: {
forgotPasswordResetPassword() {
this.passwordResetSuccess = true;
},
},
});
const authenticationStore = useCustomStore();
I can't directly add it as a plugin because it can't find an active instance of pinia.
I went through this guide: https://pinia.vuejs.org/cookbook/testing.html#unit-testing-a-store
and I also tried using jest mock as described here: https://stackoverflow.com/a/71407557/4697639
But it still failed.
If anyone has any idea how to create a custom store that can be used by the component and actually hits the custom actions, I could really use some help figuring this out. Thank you!!

Tao mentioned in the comments that this isn't a good way to do unit tests. I will mark this as resolved and fix how I do the testing.

Related

How do I properly import multiple components dynamically and use them in Nuxt?

I need to implement dynamic pages in a Nuxt + CMS bundle.
I send the URL to the server and if such a page exists I receive the data.
The data contains a list of components that I need to use, the number of components can be different.
I need to dynamically import these components and use them on the page.
I don't fully understand how I can properly import these components and use them.
I know that I can use the global registration of components, but in this case I am interested in dynamic imports.
Here is a demo that describes the approximate logic of my application.
https://codesandbox.io/s/dank-water-zvwmu?file=%2Fpages%2F_.vue
Here is a github issue that may be useful for you: https://github.com/nuxt/components/issues/227#issuecomment-902013353
I've used something like this before
<nuxt-dynamic :name="icon"></nuxt-dynamic>
to load dynamic SVG depending of the icon prop thanks to dynamic.
Since now, it is baked-in you should be able to do
<component :is="componentId" />
but it looks like it is costly in terms of performance.
This is of course based on Nuxt components and auto-importing them.
Also, if you want to import those from anywhere you wish, you can follow my answer here.
I used this solution. I get all the necessary data in the asyncData hook and then import the components in the created () hook
https://codesandbox.io/s/codesandbox-nuxt-uidc7?file=/pages/index.vue
asyncData({ route, redirect }) {
const dataFromServer = [
{
path: "/about",
componentName: 'myComponent'
},
];
const componentData = dataFromServer.find(
(data) => data.path === route.path
);
return { componentData };
},
data() {
return {
selectedRouteData: null,
componentData: {},
importedComponents: []
};
},
created() {
this.importComponent();
},
methods: {
async importComponent() {
const comp = await import(`~/folder/${this.componentData.componentName}.vue`);
this.importedComponents.push(comp.default);
}

How to use Nuxt $auth inside an axios plugin (How to add Token to all axios requests)

Im looking to use $auth inside my Nuxt project, specially inside an axios plugin.
Here is my code:
plugins/api.js
export default function ({ $axios }, inject) {
const api = $axios.create({
headers: {
common: {
Accept: 'text/plain, */*',
},
},
})
// Set baseURL to something different
api.setBaseURL('http://localhost:4100/')
// Inject to context as $api
inject('api', api)
}
Now the problem comes when I try to use $auth from #nuxtjs/auth-next package.
As stated in the docs:
This module globally injects $auth instance, meaning that you can
access it anywhere using this.$auth. For plugins, asyncData, fetch,
nuxtServerInit and Middleware, you can access it from context.$auth.
I tried the following:
This results in $auth being undefined
export default function ({ $axios, $auth }, inject) {
This one was near
export default function ({ $axios, app }, inject) {
console.log(app) //This one logs $auth in the object logged
console.log(app.$auth) // I don't understand why but this one returns undefined
My main goal here is to make use of this.$auth.strategy.token.get()and pass it (if the token exists of course) to the headers of every request made using this.$api
I have been looking for similar questions and answers but none has helped me to solve this, I could just add the token every time I write this.$api but that would increase the code unnecessarily.
Thanks in advance to all the people for your time and help.
EDIT:
Okay, now I made a test. and the next code is actually logging the $auth object correctly, it seems some time is needed to make it work but now Im afraid that using setTimeout could cause an error because I can't know exactly how much time is needed for $auth to be available.
export default function ({ $axios, app }, inject) {
setTimeout(() => {
console.log('After timeout', app.$auth)
}, 50)
EDIT 2:
So now I have made more tests, and using 0 milliseconds instead of 50 works too, so I will use setTimeout with 0 milliseconds for now, I hope anyone find a better solution or explain why $auth is not available before using setTimeout so I can decide what to do with my code.
EDIT 3:
After trying to wrap all my previous code inside setTimeout I noticed that the code fails, so that isn't a solution.
I have found a solution so I will post it so that every person that could have the same problem in the future can solve it.
It turns out that I could easily solve it using interceptors.
export default function ({ $axios, app }, inject) {
// At this point app.$auth is undefined. (Unless you use setTimeout but that is not a solution)
//Create axios instance
const api = $axios.create({
headers: {
common: {
Accept: 'application/json', //accept json
},
},
})
// Here is the magic, onRequest is an interceptor, so every request made will go trough this, and then we try to access app.$auth inside it, it is defined
api.onRequest((config) => {
// Here we check if user is logged in
if (app.$auth.loggedIn) {
// If the user is logged in we can now get the token, we get something like `Bearer yourTokenJ9F0JFODJ` but we only need the string without the word **Bearer**, So we split the string using the space as a separator and we access the second position of the array **[1]**
const token = app.$auth.strategy.token.get().split(' ')[1]
api.setToken(token, 'Bearer') // Here we specify the token and now it works!!
}
})
// Set baseURL to something different
api.setBaseURL('http://localhost:4100/')
// Inject to context as $api
inject('api', api)
}
Also Nuxt Auth itself has provided a solution for this issue:
https://auth.nuxtjs.org/recipes/extend/

How can I properly access store actions in a Vue test?

I have a test that is passing, but I don't understand why I have to write this way. Here is the test setup:
describe("Photo Due", () => {
const localVue = createLocalVue();
localVue.use(Vuex);
let store;
let actions = {
"api_data/photoAction": jest.fn(),
"api_data/selectPhoto": jest.fn(),
"modals/openCloseModal": jest.fn()
},
beforeEach(() => {
store = new Vuex.Store({
state: {
token: "abc",
GLN: "123",
GLT: "456",
isDesktop: true
},
getters: {
"auth/getToken": state => state.token,
"auth/getGLN": state => state.GLN,
"auth/getGLT": state => state.GLT,
"app/getIsDesktop": state => state.isDesktop
},
actions
});
});
...
Here is my test that calls a function, which calls an action:
it("reportPhoto", async () => {
const wrapper = factory();
wrapper.vm.reportPhoto();
wrapper.vm.$nextTick();
expect(actions["api_data/selectPhoto"]).toHaveBeenCalled(); // THIS IS THE LINE IN QUESTION
});
When the test is setup this way everything passes. But it doesn't seem right that I am defining actions outside of the store and accessing it that way in the test by calling actions["api_data/selectSnap"]. I copied this approach from a guide on from the vue-test-utils website. If I don't need to access actions and getters from the store then why not bypass the store altogether and jut define random functions that mock my vuex functionality?
I think I don't understand what is happening under the hood fully, but shouldn't I be accessing the action through the store? This is what I am having trouble doing.
My questions are 1) Do I need to access the actions through the store or am I overthinking what should be a basic test? 2) If I do need to access the action through the store, how do I go about doing that?
It really depends on what kind of test you are trying to do and if you want to test a fully functional store or just check that it gets called.
I recommend this post. There are differents approach explained along with several examples. It helped me when I needed to write some store oriented unit tests

nuxtServerInit in Modules mode does not run

I'm using nuxt.js in Universal mode and i'm trying to run nuxtServerInit() in one of my store actions, however it is not running.
In the docs it says...
If you are using the Modules mode of the Vuex store, only the primary
module (in store/index.js) will receive this action. You'll need to
chain your module actions from there.
However I don't quite understand what you need to do in order to make this work. Also if you need to use the primary module, then why would you need to use the module mode at all? Then you should just need to use the Classic mode.
store/posts.js
export const state = () => ({
loadedPosts:[]
});
export const getters = {
get(state){
return state
}
};
export const mutations = {
setPosts(state, payload){
state.loadedPosts = payload;
}
};
export const actions = {
async nuxtServerInit ({commit}){
const {data} = await axios.get('somedata')
console.log('I cannot see this comment');
commit('setPosts', data.results)
}
};
As it says in the documentation, you really need to have that function in the index.js file inside the store folder, which will not work otherwise.
here's an example of a production NuxtJs universal application with the ./store/index.js only with that file, you can easily call other stores methods by prefixing them with the file name, as the example shows auth/setAuth
I just faced the problem that nuxtServerInit was not called. The reason was in nuxt.config.js where ssr was set to false.
nuxt.config.js
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
// ssr: false, <-- Remove or out comment the line
/*
** Nuxt target
** See https://nuxtjs.org/api/configuration-target
*/
target: 'server',
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
}
}

Page reload causes Vuex getter to return undefined

Using Vue.js (Vuetify for FE).
A page reload causes the getter in Vuex to fail with pulling required data from the store. The getter returns undefined. The code can be found on GitHub at: https://github.com/tineich/timmyskittys/tree/master/src
Please see the full details on this issue at timmyskittys.netlify.com/stage1. This page has complete info on the issue and instructions on how to view the issue.
Note, there is mention of www.timmyskittys.com in the issue description. This is the main site. timmyskittys.netlify.com is my test site. So, they are the same for all intents and purposes. But, my demo of this issue is at the Netlify site.
I read the complete issue in the website you mentioned. It's a generic case.
Say, for cat details page url: www.timmyskittys.com/stage2/:id.
Now in Per-Route Guard beforeEnter() you can set the cat-id in store. Then from your component call the api using the cat-id (read from getters)
I found the solution to my issue:
I had to move the call of the action which calls the mutation that loads the .json file (dbdata.json) into a computed() within App.vue. This was originally done in Stage1.vue.
Thanks all for responding.
I had the same issue and my "fix" if it can be called that was to make a timer, so to give the store time to get things right, like so:
<v-treeview
:items="items"
:load-children="setChildren"
/>
</template>
<script>
import { mapGetters } from 'vuex'
const pause = ms => new Promise(resolve => setTimeout(resolve, ms))
export default {
data () {
return {
children: []
}
},
computed: {
...mapGetters('app', ['services']),
items () {
return [{
id: 0,
name: 'Services',
children: this.children
}]
}
},
methods: {
async setChildren () {
await pause(1000)
this.children.push(...this.services)
}
}
}
</script>
Even though this is far from ideal, it works.