Oazapfts.fetchJson is not define while in Jest test - vue.js

I'm trying to create test for call api fn but i get - oazapfts.fetchJson is not define while runing the test.
the prject is in vue3 with quasar, oazapftp and Jest.
So my component looks like this:
import { putAdduser } from '../components/models'
import { handle } from 'oazapfts'
import { useRouter } from 'vue-router'
import { Notify } from 'quasar'
async function createUser() {
//if pass is ok ten:
await handle(putAdduser({ name: name.value, pass: password.value }), {
201(data) {
Notify.create({
message: data.message,
color: 'green-10',
})
router.push('/login')
},
409(message) {
Notify.create(message)
},
500(message) {
Notify.create(message)
},
})
return 'hello'
}
the api handler is :
export function putAdduser(user?: User, opts?: Oazapfts.RequestOpts) {
return oazapfts.fetchJson<{
status: 201;
data: Message;
} | {
status: 409;
data: Message;
} | {
status: 500;
data: Message;
}>("/dosome", oazapfts.json({
...opts,
method: "PUT",
body: user
}));
}
i've tried:
import { describe, expect, it } from '#jest/globals'
import { installQuasarPlugin } from '#quasar/quasar-app-extension-testing-unit-jest'
import { mount } from '#vue/test-utils'
import { Notify } from 'quasar'
import myComp from '../../../src/components/myComp'
installQuasarPlugin({ plugins: { Notify } })
const wrapper = mount(myComp)
const { vm } = wrapper
it('add User', async () => {
const res = await vm.createUser()
expect(res).toBe('hello')
})
My problem is how to let Jest use this call.

Related

Vue 3 / Pinia: how to handle error, loading and data properly

Using Fetch, and not axios, I wanna receive data in my components like that:
const userStore = useAuthStore();
const { user, error, loading } = storeToRefs(userStore);
userStore.getMe();
But I don't know how to do that.
I want that to have directly the error, data and loading state in one line because I think it's better.
But I don't wanna declare a loading like this in the store:
export const useAuthStore = defineStore({
id: "auth",
state: () => ({
user: {} as User,
loading: false,
}),
Because if I call another method related to this store (User), it will be the same Loading state. So this loading state (even the error state) will be in conflict.
If I would use Javascript and no Typescript, I would definetly replace this.user like this when fetching or when error (in the store):
async getMe() {
this.user = { loading: true };
try {
return await AuthService.getMe();
} catch (error) {
if (error) {
his.user = { error };
}
}
},
Because it's TypeScript, I can't replace the "user" state like that as I have set an Interface.
All I want, is to return a Data, Erros, Loading related to an unique action (not related to a state).
auth store:
import { defineStore } from "pinia";
import AuthService from "#/api/modules/auth";
interface User {
email: string;
first_name: string;
last_name: string;
force_password_change: boolean;
groups: string[];
has_2fa_enabled: boolean;
is_staff: boolean;
lang: string;
last_password_change: string;
permissions: string[];
session_expiry_date: string;
uid: string;
}
export const useAuthStore = defineStore({
id: "auth",
state: () => ({
user: {} as User,
loading: false,
}),
actions: {
async getMe() {
// this.user = { loading: true };
try {
return await AuthService.getMe();
} catch (error) {
if (error) {
// this.user = { error };
}
}
},
},
});
service:
import { Api } from "../apiSettings";
class AuthService {
async getMe(): Promise<any> {
return await Api.get("api/auth/me/");
}
}
export default new AuthService();
App.vue:
<script setup lang="ts">
import { useAuthStore } from "#/stores";
import { storeToRefs } from "pinia";
const userStore = useAuthStore();
const { user } = storeToRefs(userStore);
userStore.getMe();
console.log(user.value);
</script>
You can define a separate object for loading and error states and return it from the store's action along with the actual data. You can then use object destructuring to extract the loading, error, and data states in your component. Here's an example:
Auth store:
import { defineStore } from "pinia";
import AuthService from "#/api/modules/auth";
interface RequestState {
loading: boolean;
error: Error | null;
}
export const useAuthStore = defineStore({
id: "auth",
state: () => ({
user: {} as User,
}),
actions: {
async getMe(): Promise<{ data: User; requestState: RequestState }> {
const requestState: RequestState = {
loading: true,
error: null,
};
try {
const data = await AuthService.getMe();
requestState.loading = false;
return { data, requestState };
} catch (error) {
requestState.loading = false;
requestState.error = error;
return { data: {} as User, requestState };
}
},
},
});
Component:
<script setup lang="ts">
import { useAuthStore } from "#/stores";
import { storeToRefs } from "pinia";
const userStore = useAuthStore();
const { user, requestState } = storeToRefs(userStore);
// Call the action to get user data
userStore.getMe();
console.log(user.value); // {} - initial value of user
console.log(requestState.value); // { loading: true, error: null }
// Use object destructuring to get the actual user data, loading and error states
const { data, loading, error } = requestState.value;
if (loading) {
// Render a loading state
} else if (error) {
// Render an error state
} else {
// Render the user data
console.log(data);
}
</script>

Why Vitest mock Axios doesn't work on vuex store testing?

I have the same issue than this post: Why does vitest mock not catch my axios get-requests?
I would like to test my vuex store on vuejs and it works for getters etc but not for actions part with axios get request.
I don't know if it's a good practice to test vuex store than the component in Vue ?
But I guess I need to test both, right ?
a project https://stackblitz.com/edit/vitest-dev-vitest-nyks4u?file=test%2Ftag.spec.js
my js file to test tag.js
import axios from "axios";
const actions = {
async fetchTags({ commit }) {
try {
const response = await axios.get(
CONST_CONFIG.VUE_APP_URLAPI + "tag?OrderBy=id&Skip=0&Take=100"
);
commit("setTags", response.data);
} catch (error) {
console.log(error);
return;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
then my test (tag.spec.js)
import { expect } from "chai";
import { vi } from "vitest";
import axios from "axios";
vi.mock("axios", () => {
return {
default: {
get: vi.fn(),
},
};
});
describe("tag", () => {
test("actions - fetchTags", async () => {
const users = [
{ id: 1, name: "John" },
{ id: 2, name: "Andrew" },
];
axios.get.mockImplementation(() => Promise.resolve({ data: users }));
axios.get.mockResolvedValueOnce(users);
const commit = vi.fn();
await tag.actions.fetchTags({ commit });
expect(axios.get).toHaveBeenCalledTimes(1);
expect(commit).toHaveBeenCalledTimes(1);
});
});
It looks like some other peolpe have the same issues https://github.com/vitest-dev/vitest/issues/1274 but it's still not working.
I try with .ts too but I have exactly the same mistake:
FAIL tests/unit/store/apiObject/tag.spec.js > tag > actions - fetchTags
AssertionError: expected "spy" to be called 1 times
❯ tests/unit/store/apiObject/tag.spec.js:64:24
62| await tag.actions.fetchTags({ commit });
63|
64| expect(axios.get).toHaveBeenCalledTimes(1);
| ^
65| expect(commit).toHaveBeenCalledTimes(1);
66| });
Expected "1"
Received "0"
Thanks a lot for your help.
I finally found the mistake, it was on my vitest.config.ts file, I have to add my global config varaible for my api: import { config } from "#vue/test-utils";
import { defineConfig } from "vitest/config";
import { resolve } from "path";
var configApi = require("./public/config.js");
const { createVuePlugin } = require("vite-plugin-vue2");
const r = (p: string) => resolve(__dirname, p);
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
define: {
CONST_CONFIG: configApi,
},
plugins: [createVuePlugin()],
resolve: {
alias: {
"#": r("."),
"~": r("."),
},
// alias: {
// "#": fileURLToPath(new URL("./src", import.meta.url)),
// },
},
});

Vue Test Utils / apollo-composable check if useMutation was called

I have the component AddEmployeeModal.vue where I want to test if UseMutation from vue/apollo-composable gets called
// AddEmployeeModal.vue:
import { provideApolloClient, useMutation, useQuery } from '#vue/apollo-composable';
import { apolloClient } from '#/apollo/ApolloClient';
import {
CreateEmployeeDocument,
UpdateEmployeeDocument,
} from '#/generated/graphql';
provideApolloClient(apolloClient);
const { mutate: addEmployee } = useMutation(CreateEmployeeDocument);
const { mutate: updateEmployee } = useMutation(UpdateEmployeeDocument);
I think I need to mock the useMutation function but I'm not sure how to mock a library function.
import { useQuery, useMutation } from '#vue/apollo-composable'
jest.mock('#vue/apollo-composable', () => ({
__esModule: true,
useQuery: jest.fn(),
useMutation: jest.fn()
}))
useQuery.mockImplementation(() => ({
result: { value: testRes }
}))
useMutation.mockImplementation(() => ({
onDone: jest.fn()
}))

How to use Pinia with Nuxt, composition-api (vue2) and SSR?

I'm trying to get Pinia to work in Nuxt with SSR (server-side rendering).
When creating a page without Pinia, it works:
<script>
import { reactive, useFetch, useContext } from '#nuxtjs/composition-api'
export default {
setup() {
const { $axios } = useContext()
const invitesStore = reactive({
invites: [],
loading: true,
})
useFetch(async () => {
invitesStore.loading = true
await $axios.$get('invite/registermember').then((result) => {
invitesStore.loading = false
invitesStore.invites = result.invites
})
})
return {
invitesStore,
}
},
}
</script>
But when introducing Pinia, I get the error "Converting circular structure to JSON --> starting at object with constructor 'VueRouter'"
I'm using Pinia this way:
// /store/invitesStore.js
import { defineStore } from 'pinia'
// useStore could be anything like useUser, useCart
export const useInvitesStore = defineStore({
// unique id of the store across your application
id: 'storeId',
state() {
return {
invites: [],
loading: true,
}
},
})
<script>
import { useInvitesStore } from '#/store/invitesStore'
import { reactive, onMounted, useFetch, useContext } from '#nuxtjs/composition-api'
export default {
setup() {
const { $axios } = useContext()
const invitesStore = useInvitesStore()
useFetch(async () => {
invitesStore.loading = true
await $axios.$get('invite/registermember').then((result) => {
invitesStore.loading = false
invitesStore.invites = result.invites
})
})
return {
invitesStore,
}
},
}
</script>
Is it possible to get this to work? How?

How to integrate paypal Payment Button Vuejs3 Composition API (setup function)

I'm trying to integrate PayPal buttons with my Vuejs3 project using Composition API (setup ) but all what i get is errors i try to integrate it without using setup and its working fine i leave the working script down
the esseu is i couldent pass data from data to methodes
<script>
import { inject, onMounted, ref } from "vue";
export default {
data() {
return {
loaded: false,
paidFor: false,
product: {
price: 15.22,
description: "leg lamp from that one movie",
img: "./assets/lamp.jpg",
},
};
},
setup() {
const store = inject("store");
console.log(store.state.prodects_in_cart);
return { store };
},methods:{
setLoaded: function() {
this.loaded = true;
paypal_sdk
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: this.product.description,
amount: {
currency_code: "USD",
value: this.product.price
}
}
]
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.data;
this.paidFor = true;
console.log(order);
},
onError: err => {
console.log(err);
}
})
.render(this.$refs.paypal);
}
},
mounted: function() {
const script = document.createElement("script");
script.setAttribute('data-namespace',"paypal_sdk");
script.src ="https://www.paypal.com/sdk/js?client-id=Here i pute my Client Id";
script.addEventListener("load", this.setLoaded);
document.body.appendChild(script);
},
};
</script>
the error i get when i use setup() is
The error image
my script using setup()
setup() {
const store = inject("store");
const paypal = ref(null);
let loaded = ref(false);
let paidFor = ref(false);
const product = {
price: 15.22,
description: "leg lamp from that one movie",
img: "./assets/lamp.jpg",
};
onMounted: {
const script = document.createElement("script");
script.setAttribute("data-namespace", "paypal_sdk");
script.src =
"https://www.paypal.com/sdk/js?client-id=AXDJPmFjXpXm9HMXK4uZcW3l9XrCL36AxEeWBa4rhV2-xFcVYJrGKvNowY-xf2PitTSkStVNjabZaihe";
script.addEventListener("load", ()=>{
loaded = true;
console.log('hello adil');
paypal_sdk
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: 'this is product description',
amount: {
currency_code: "USD",
value: 120.00,
},
},
],
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.data;
this.paidFor = true;
console.log(order);
},
onError: (err) => {
console.log(err);
},
})
.render(paypal);
});
document.body.appendChild(script);
}
return { store ,paypal};
}
paypal is a ref. You're currently passing to paypal_sdk the ref itself and not the inner value, which would be the template ref's element. To fix this, pass the ref's .value.
Your onMounted code is not properly invoked, as it must be passed a callback.
import { onMounted, ref } from 'vue'
export default {
setup() {
const paypal = ref(null)
onMounted(/* 2 */ () => {
const script = document.createElement('script')
//...
script.addEventListener('load', () => {
paypal_sdk
.Buttons(/*...*/)
.render(paypal.value) /* 1 */
})
})
return {
paypal
}
}
}
The reason why you are getting that error is because you are using option Api onMounted life cycle hook, instead of doing that use the vue 3 life cycle hooks for onMounted.
First you will have to import it from vue like this.
<script>
import {onMounted} from 'vue'
then you are going to use it like this.
return it as a call back function
onMounted(() => {
//all your code should placed inside here and it will work
})
</script>
Here is my answer using the paypal-js npm package
<template>
<div ref="paypalBtn"></div>
</template>
<script>
import { onMounted, ref } from 'vue';
import { loadScript } from '#paypal/paypal-js';
const paypalBtn = ref(null);
onMounted(async () => {
let paypal;
try {
paypal = await loadScript({
'client-id': 'you_client_id_goes_here',
});
} catch (error) {
console.error('failed to load the PayPal JS SDK script', error);
}
if (paypal) {
try {
await paypal.Buttons().render(paypalBtn.value);
} catch (error) {
console.error('failed to render the PayPal Buttons', error);
}
}
});
</script>