Working on Jest with timers for vee validate - vue.js

I am trying to determine if my button is disabled but the disabled property keeps returning undefined. I have checked and followed the instruction in https://logaretm.github.io/vee-validate/advanced/testing.html#testing-validationobserver-debounced-state but it does not work accordingly. I think it is the way I implemented the Jest mock timers. Below is my code
import { mount, createLocalVue } from "#vue/test-utils";
import { ValidationObserver, ValidationProvider } from "vee-validate";
import { BootstrapVue } from "bootstrap-vue";
import Login from "#/pages/login.vue";
const localVue = createLocalVue();
localVue.component("ValidationObserver", ValidationObserver);
localVue.component("ValidationProvider", ValidationProvider);
localVue.use(BootstrapVue);
jest.useFakeTimers();
describe("Login", () => {
test("if username is not entered", () => {
const wrapper = mount(Login, { localVue });
const username = wrapper.find("#username");
const password = wrapper.find("#password");
const login = wrapper.find("#login");
jest.advanceTimersByTime(50);
username.element.value = "test";
password.element.value = "test";
expect(login.attributes("disabled")).toBe(true);
});
});
Basically, the behaviour of my login form is.. The login button is disabled until the user enters a value on the username and password inputs.
I am also using NuxtJS for this

You would still need to wait for pending promises as well as using flush-promises. The docs suggest doing both as a part of a custom flush method.
async function flush() {
await flushPromises();
jest.runAllTimers();
}

Related

CASL - Vue 3 - Element not showing for role

I am having a bit of a challenge implementing CASL in my app.
I have created the following composable useAppAbility ("hook") that defines all the rules:
import { AbilityBuilder, createMongoAbility, subject } from "#casl/ability";
import { useAbility } from "#casl/vue";
const service = {};
const user = {};
const subscription = {};
const invoice = {};
const account = {};
const ability = createMongoAbility();
const ROLES = ["admin", "account_owner", "beneficiary", "super_admin"];
const defineAbilityFor = (role: Object) => {
const { can, rules } = new AbilityBuilder(createMongoAbility);
const is = (r: string) => {
return ROLES.indexOf(r) >= ROLES.indexOf(role);
};
if (is("admin")) {
can("add", subject("User", user));
can("remove", subject("User", user));
}
return ability.update(rules);
};
export { defineAbilityFor, ability, subject };
export const useAppAbility = () => useAbility();
Added the plugin to the main.ts:
import { ability } from "#/composables/useAppAbility";
import { abilitiesPlugin } from "#casl/vue";
createApp(App)
.use(abilitiesPlugin, ability, {
useGlobalProperties: true,
})
//stuff
.mount("#app");
And then, I found that using the beforeEach hook in the router and passing in the user before each route was the simplest way to deal with page load and SPA routing.
I have therefore added the following to my router/index.ts:
import { ability, defineAbilityFor } from "#/composables/useAppAbility";
import useAuth from "#/composables/useAuth";
const {
getUserByClaims,
} = useAuth();
// routes
router.beforeEach(async (to, _from, next) => {
defineAbilityFor(getUserByClaims.value.roles)
})
At this stage I can verify that the user is being passed properly to the defineAbilityFor function and when using the ability.on("update") hook to log the rules object, I have the following output:
Which seems to confirm that the rules for this user are built and updated correctly?
However, when trying to display a button for the said admin in a component, the button does not show.
MyComponent.vue:
<script setup lang="ts">
import { useAppAbility, subject } from "#/composables/useAppAbility";
const { can } = useAppAbility();
</script>
<template>
<div v-if="can('add', subject('User', {}))">TEST FOR CASL</div> <!-- DOES NOT SHOW-->
</template>
Not sure where to go from there, any help would be appreciated.
Thanks

Auto Refresh for Vuex

I would like to implement a auto refresh feature for my VueX store.
Everything the user refresh their browser, an actions in VueX store will be triggered to load the user profile from API call.
Is't possible to achieve that?
import apiService from "#/services/apiService";
import apiUrls from "#/services/apiUrls";
import { getToken } from "#/services/jwtService";
// Code to run actions when user refresh
getToken() !== null ? this.actions.getUserProfile() : "";
const state = {
userProfile: {},
};
const getters = {
userProfile: (state) => state.userProfile,
};
const actions = {
async getUserProfile({ commit }) {
console.log("here");
try {
let response = await apiService.get(apiUrls.PROFILE);
commit("setUserProfile", response.data.data);
} catch (error) {
console.log(error);
}
},
};
Thank you.
A user refresh means that the application will be re-executed. So basically main.js will be re-executed, App.vue re-created, etc.
That means just have to call your code in main.js or in a created lifecycle hook of any top-level component.
By top-level component I means any component which is created early in the app

How can I import a redux saga yield into my detox + jest test file. I need gain access to data stored in the redux store in my test setup

This is a react-native application and I am currently writing some end-to-end testing.
A token is stored in the redux store shown below and I am testing the login functionality using detox/jest. I need to detect if the token exists in the store in my login.spec.js . If the token exists I want to wipe it from the store so the user is not logged in automatically when i reload the app to take the user back to another scene. The main function in question is the refreshUserToken() and line:-
const { refresh_token } = yield select(token);
Here is the redux saga file User.js located at:-MyApp/App/Sagas/User.js
import { call, put, takeEvery, select } from "redux-saga/effects";
import Config from "MyApp/App/Config";
import API from "MyApp/App/Services/API";
import { when } from "MyApp/App/Helpers/Predicate";
import Credentials from "MyApp/App/Helpers/Credentials";
import ActionCreator from "MyApp/App/Actions";
const appendPayload = payload => {
return {
...payload,
// Removed because no longer needed unless for testing purposes.
// username: Config.TEST_USERNAME,
// password: Config.TEST_PASSWORD,
client_id: Config.CLIENT_ID,
client_secret: Config.CLIENT_SECRET,
};
};
const token = state => state.token;
const user = state => state.user;
const attemptUserLogin = function*(action) {
const { payload } = action;
const login = "/oauth/token";
const grant_type = "password";
const loginPayload = appendPayload(payload);
action.payload = {
...loginPayload,
grant_type,
};
yield attemptUserAuthorisation(login, action);
};
const attemptUserRegister = function*(action) {
const register = "/api/signup";
const { payload } = action;
yield Credentials.save(payload);
yield put(ActionCreator.saveUserCredentials(payload));
yield attemptUserAuthorisation(register, action);
};
const refreshUserToken = function*(action) {
const login = "/oauth/token";
const grant_type = "refresh_token";
const { refresh_token } = yield select(token);
action.payload = {
...action.payload,
grant_type,
refresh_token,
};
yield attemptUserAuthorisation(login, action);
};
const watchExampleSaga = function*() {
yield takeEvery(ActionCreator.AUTO_USER_LOGIN, autoUserLogin);
yield takeEvery(ActionCreator.USER_LOGIN, attemptUserLogin);
yield takeEvery(ActionCreator.USER_REGISTER, attemptUserRegister);
yield takeEvery(ActionCreator.USER_REFRESH_TOKEN, refreshUserToken);
};
export default watchExampleSaga;
Here is my detox/jest spec file located at:-MyApp/App/e2e/login.spec.js
describe('Login Actions', () => {
it('Should be able to enter an email address', async () => {
await element(by.id('landing-login-btn')).tap()
const email = 'banker#dovu.io'
await element(by.id('login-email')).tap()
await element(by.id('login-email')).replaceText(email)
});
it('Should be able to enter a password', async () => {
const password = 'secret'
await element(by.id('login-password')).tap()
await element(by.id('login-password')).replaceText(password)
});
it('Should be able to click the continue button and login', async () => {
await element(by.id('login-continue-btn')).tap()
await waitFor(element(by.id('dashboard-logo'))).toBeVisible().withTimeout(500)
// If token exists destroy it and relaunch app. This is where I need to grab the token from the redux saga!
await device.launchApp({newInstance: true});
});
})
This is how I handled a similar scenario:
in package.json scripts:
"start:detox": "RN_SRC_EXT=e2e.tsx,e2e.ts node node_modules/react-native/local-cli/cli.js start",
In my detox config:
"build": "ENVFILE=.env.dev;RN_SRC_EXT=e2e.tsx,e2e,ts npx react-native run-ios --simulator='iPhone 7'",
That lets me write MyFile.e2e.tsx which replaces MyFile.tsx whilst detox is running
In the test version of that component I have buttons which are tapped in the tests and the buttons dispatch redux actions
Looks like this actually cant be done unless someone can give me a solution other than mocking the state which still wouldn't work in this case my app checks for real states to auto login.
I did get to the stage of creating a new action getUserToken and exporting that into my jest file. However the action returns undefined because the jest file requires a dispatch method like in containers.js. If anyone could provide me with a method of this using jest I would be very happy.

How to update state in reducer

I have a problem with my reducer. I am using redux to create a login page. I have successfully logged in yet I failed to navigate to the next page. The state in my reducer is not updated. How do I solve this?
This is how I wrote my reducer:
import { LOGIN_SUCCESS } from '../actions/types';
const INITIAL_STATE={
isLoginSuccess:false,
}
export default function (state=INITIAL_STATE, action){
switch(action.type){
case LOGIN_SUCCESS:
return {
isLoginSuccess : true
}
default:
return INITIAL_STATE;
}
}
This is how I wrote my action:
import axios from 'axios';
import * as helper from '../common';
import { LOGIN_SUCCESS } from './types';
export const attemptLogin = (username, password) => async dispatch => {
let param = {
txtNomatrik: username,
txtPwd: password,
public_key: helper.PUBLIC_KEY,
secret_key: helper.SECRET_KEY
}
console.log(`${helper.ROOT_API_URL}/v1/basic/ad/std/login`)
let login_res = await
axios.post(`${helper.ROOT_API_URL}/v1/basic/ad/std/login`, param)
console.log(login_res.data);
if (login_res.data.status == 'Successful Login') {
const { login } = login_res.data;
await AsyncStorage.seItem('Login_token', username);
await AsyncStorage.setItem('profile', JSON.stringify(login));
dispatch({ type: LOGIN_SUCCESS, payload: { isLoginSuccess : true } });
}
}
I want to use the isLoginSuccess in my index file to navigate login to the next page like this:
componentWillReceiveProps(nextProps){
if(nextProps.isLoginSuccess){
this.props.navigation.navigate('logged');
}
}
Below is how I connect redux:
const mapStateToProps = ({ auth }) => {
return {
isLoginSuccess: auth.isLoginSuccess
}
}
export default connect(mapStateToProps, actions)(LoginScreen);
Below is my combineReducer file:
import {combineReducers} from 'redux';
import news from './welcome_reducer';
import auth from './login_reducer';
export default combineReducers({
news,
auth
})
How do I solve this? I feel lost now, have tried a lot of ways to solve it . The Api call for login is successful but the action is not dispatched at all. I can't update the state and I can't navigate to the next page. Please help me

navigation is not working using redux-saga

I am trying to navigate to Main Screen and code still work fine if the JS DEBUGGER is ON(running) but the problem is when i try to run my application when JS DEBUGGER is OFF(disable) and try to login at that time "yield put(NavigationActions.navigate({ routeName: 'Main' }));"
this piece code is not redirecting to Main screen.
Given below is my code:
import { NavigationActions } from 'react-navigation';
import { call, put, takeEvery, take } from 'redux-saga/effects';
import { getFirebase } from 'react-redux-firebase';
export function* watchLoginAsync({email, password}) {
try {
const response = yield getFirebase().login({email,password});
if (response.uid) {
// dispatchToMain();
yield put(NavigationActions.navigate({ routeName: 'Main' }));
// yield put({type: LOGIN_SUCCESS });
} else {
yield put({type: LOGIN_FAIL, error: 'Something went wrong seriously!!'});
}
} catch(err => console.log(err))
}
export default function* watchLogin() {
yield takeEvery(LOGIN_REQUESTING, watchLoginAsync);
}
And this the store.js file(where i have integrated redux-saga with react-redux-firebase)
const sagaMiddleware = createSagaMiddleware();
const middleware = [ sagaMiddleware ];
const firebaseConfig = {
apiKey: '******',
authDomain: '****',
databaseURL: '****',
projectId: '****',
storageBucket: '****',
messagingSenderId: '****',
};
const reduxFirebaseConfig = {
userProfile: 'users',
enableLogging: true,
enableRedirectHandling: false,
};
// Add redux Firebase to compose
const createStoreWithFirebase = compose(
reactReduxFirebase(fbConfig, reduxFirebaseConfig),
applyMiddleware(...middleware)
)(createStore);
// Add Firebase to reducers
const rootReducer = combineReducers({
firebase: firebaseStateReducer,
......
});
// Create store with reducers and initial state
const initialState = {}
export default createStoreWithFirebase(rootReducer, initialState);
// when calling saga, pass getFirebase
sagaMiddleware.run(Sagas, getFirebase)
NOTE:
code is totally working fine if JS DEBUGGER IS ON iOS simulator while app is running.
NOTE: code is totally working fine if JS DEBUGGER IS ON iOS simulator while app is running.
In general debugging mode differs from normal execution by timings - e.g. in debug mode some asynchronous action is performed while you are watching values on breakpoint, and also execution in debug mode is much slower due interception ES262-engine actions. You can try to debug more verbosely by using console.log instructions.
Also, supplied source code
const createStoreWithFirebase = compose(
reactReduxFirebase(fbConfig, reduxFirebaseConfig),
applyMiddleware(...middleware)
)(createStore);
does not include mandatory saga initial invoke operation, like sagaMiddleware.run(rootSaga), what's why maybe nothing is executed.
In original documentation it's present: https://github.com/prescottprue/react-redux-firebase/blob/master/docs/recipes/redux-saga.md