I want to test the if the localStorage is cleared down whenever I call my function.
Component
ngOnInit() {
// Logout on reaching login screen so we can login
this.authService.logout();
}
authService
logout() {
// remove logged in user from localStorage
localStorage.removeItem('currentUser');
}
TEST
fit('ngOnInit should logout user stored in localStorage', () => {
// Exmample data stored as token
localStorage.setItem('token', 'someUserToken');
component.ngOnInit();
expect(localStorage.getItem('token')).toEqual('{}');
});
Is there any way I can achieve this?
Unit testing should only concern the feature you're testing.
This means that you should not check if the local storage is cleared : that's not what your component does. Your component calls your auth service, which then clears the storage.
This means you should test if the correct method is called.
it('ngOnInit should call auth.logout', () => {
spyOn(component['authService'], 'logout');
component.ngOnInit();
expect(component['authService'].logout).toHaveBeenCalled();
});
Related
I am working on a webapp with the frameworks Vue 3 and firebase. As stroe I use Pinia. For authentication I use firebase, which works well so far, users can register and log in. As soon as a user logs in, his data is stored in the pinia store.
However, as soon as the user logs in and reloads the page, the data in the pinia store is lost. If I use the firebase function onAuthStateChanged() there is still a user logged in.
My first solution was to call the onAuthStateChanged() function every time the app is started and the pinia store is then filled with the required data. I already tried to call the onAuthStateChanged() function in my firebase config file, but the pinia store is not initialized here yet.
At which point in the Vue app must the onAuthStateChanged() function be called so that the user is automatically logged in after a refresh and I can write the user data to the Pinia store?
I am not sure what you have tried but I know this will work. You can, of course, move the onAuthStateChanged out of your store and it will still work. Keep in mind you will have to use a watcher or computed prop to track store.user and update the UI.
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const auth = getAuth();
onAuthStateChanged(auth, async () => {
const store = useStore();
store.setUser();
});
const useStore = defineStore('store', {
state: () => ({
user: null
}),
actions: {
setUser() {
// Set user here
// this.user = ...
}
}
});
I use keycloak on my React project as authentication and authorization tool and want to store some data of the user inside a React Context to access data like username etc. This context should update when I log in or log out.
My first approach was to use two events called onAuthSuccess and onAuthLogout but it seems that it will not be fired at all.
To test this approach I execute a console.log. Unfortunately nothing happens if I log in via keycloak.login() and logout via keycloak.logout().
import Keycloak from 'keycloak-js';
import configData from './config.json';
const keycloak = new Keycloak(configData);
keycloak.onAuthSuccess = () => {
console.log('log in!');
}
keycloak.onAuthLogout = () => {
console.log('log out');
}
export default keycloak
Any ideas what the problem could be?
I have started learning state management using NGXS. So far everything is fine but have few questions regarding some scenarios like -
If a Mat Dialog box is open (or any div - here I've both the scenarios in my project) and from inside it an API is called, how can I close the dialog only if API returns success?
Suppose a user logs out, how can I reset the states to default values?
For the first case below is my code for the state, action & dispatcher:
abc.action.ts
export class AddExamCategory {
static readonly type = '[ExamCategory] Add';
constructor(public payload: ExamCategory) {}
}
abc.state.ts
export interface ExamCategoryStateModel {
examCategoryList: ExamCategory[];
}
#State<ExamCategoryStateModel>({
name: 'examCategory',
defaults: {
examCategoryList: []
}
})
#Injectable()
export class ExamCategoryState {
constructor(private _adminService: AdminService) {}
#Action(AddExamCategory)
addExamCategory({ getState, patchState }: StateContext<ExamCategoryStateModel>, { payload }: AddExamCategory) {
return this._adminService.createExamCategory(payload).pipe(tap(response => {
patchState({ examCategoryList: [] }); // Want to close the modal/div after this part. If API returns ERROR do not close.
}));
}
}
abc.component.ts
this.store.dispatch(new AddAdminUser({ ...this.adminRegistrationForm.value, password: this.password.value }))
.subscribe(response => {
this.store.dispatch(new GetAdminUsers());
this.dialogRef.close(true)
});
Currently it's like this but it closes no matter what's the status of API.
For the second case, in the service where I have written the logic for logout() I have written like this: this.store.reset({}). Though it's resetting the state but not with the default values in it. I have multiple states to reset on this single logout method.
How to work on these scenarios?
You can add extra property on your state to track the requesting state of your application ('requesting','idle') [you can create extra states as needed to track 'success' and 'error' response from the server]
when dispatch GetAdminUsers set the value of the newely added state to requesting and at GetAdminUsersComplete set the value to idle
subscribe to a selector that's read the state on your ngOnInit and call dialogRef.clse(true) inside of it. like following:
this.store
.pipe(
select(selectors.selectRequestState),
skip(1) //only start tracking after request created
)
.subscribe(result => {
if (result == 'idle')
this.dialogRef.close()
});
example: https://stackblitz.com/edit/angular-ivy-4ofc3q?file=src/app/app.component.html
Reset State
I don't think there is a simple way to reset the state with the store. You need to move through all your state features and implement reset action that set the state to initial state.
the simplest solution is to refresh the browser page after user logout using location.reload();
if you keep the store inside localstorage you need to remove it first then do the reload
the easiest way to accomplish that is with an effect, after you dispatch your action you should be able to trigger other action like "UsserAddedSuccessfully" and read it after that close that modal.
read this question for more detail.
I have a login service component which has a behavioral subject _loginEmailId" and the method updateLoginEmailId(email:string)` to update its value as following.
private _loginEmailId = new BehaviorSubject<string>("")
currentLoginEmailId = this._loginEmailId.asObservable()
private _isLoggedIn = new BehaviorSubject<boolean>(false);
isLoggedIn$ = this._isLoggedIn.asObservable();
changeLoggedInStatus(state:boolean){
this._isLoggedIn.next(state);
}
updateLoginEmailId(email:string){
this._loginEmailId.next(email);
}
login(email:string, password:string){
//returns observable
}
In Login Component I am subscribing to a login method from Login service component by passing a email ID and password captured from a reactive form. Inside this subscribe function, after receiving the response I am trying to update the _loginEmailId in the login service, by calling updateLoginEmailId(email:string) so that I can use it in another component. But I am not able to pass this.loginEmail to the updateLoginEmailId(email:string).
When I log this.loginEmail inside subscribe it logs null, outside subscribe it logs the correct value.
I know, this is bacause of this not referring to the component.
I have seen that=this hack, that did not work either.
How can I solve this.
My Login Component looks like this
export class LoginComponent{
loginEmail:string;
loginPassword:string;
}
login(form: NgForm){
this.loginService.login(this.loginEmail, this.loginPassword).subscribe(
(response) => {
this.loginService.changeLoggedInStatus(true); //This works
this.loginService.updateLoginEmailId(this.loginEmail); // The function is getting invoked
but the value for this.loginEmail is null.
this.router.navigate(['/home']); //This works
},
error => {
console.log(error);
}
);
form.resetForm();
}
subscription is asynchronous, meaning it happens after the form is reset. you should move resetting the form inside of the subscription callback.
I'm testing a website with a login page and then other pages only visible after login.
I created a login_page.js model as follows:
// my_login_page_model.js
import { Selector, t } from 'testcafe';
export default class LoginPage {
constructor () {
this.email = Selector('#email');
this.password = Selector('#password');
this.signin = Selector('#signinButton');
}
}
and then I created similar page models for the pages after login.
On my test file, I then instantiate a login page object, run the login page test
// my_test_spec.js
test('login in', async t => {
await t
.typeText(loginPage.email, config.name)
.typeText(loginPage.password, config.password)
.click(loginPage.signin);
await t.expect(getURL()).contains(pageUrl);
});
test('second test', async t => {
console.log('Creating a new experience');
await t
.click(// click a button on the following page);
});
The problem is the second test starts from the login page and of course, it fails because it can't find the ids of the page after login.
How can this be done?
Every test starts from the scratch and that means clean profile, that why you are not authorized, when second test starts. The simplest way to fix is to move you login code at the beginning of your second test. However, it's not good practice, because you probably need authorization before majority of your test. To solve this, you can choose one of this:
1) Http Auth for some cases
2) Roles
3) Use beforeEach Hook. Just put Login code to this hook and it's will execute before every test in fixture.