diff options
Diffstat (limited to 'Timeline/ClientApp')
6 files changed, 51 insertions, 33 deletions
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts index be6631eb..8d081402 100644 --- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts +++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts @@ -82,33 +82,40 @@ describe('InternalUserService', () => { }))); }); - it('login should work well', () => { + describe('login should work well', () => { const mockUserCredentials: UserCredentials = { username: 'user', password: 'user' }; - const service: InternalUserService = TestBed.get(InternalUserService); + function createTest(rememberMe: boolean) { + return () => { + const service: InternalUserService = TestBed.get(InternalUserService); - service.tryLogin(mockUserCredentials).subscribe(result => { - expect(result).toEqual(mockUserInfo); - }); + service.tryLogin({ ...mockUserCredentials, rememberMe: rememberMe }).subscribe(result => { + expect(result).toEqual(mockUserInfo); + }); - const httpController = TestBed.get(HttpTestingController) as HttpTestingController; + const httpController = TestBed.get(HttpTestingController) as HttpTestingController; - httpController.expectOne((request: HttpRequest<CreateTokenRequest>) => - request.url === createTokenUrl && request.body !== null && - request.body.username === mockUserCredentials.username && - request.body.password === mockUserCredentials.password).flush(<CreateTokenResponse>{ - token: mockToken, - userInfo: mockUserInfo - }); + httpController.expectOne((request: HttpRequest<CreateTokenRequest>) => + request.url === createTokenUrl && request.body !== null && + request.body.username === mockUserCredentials.username && + request.body.password === mockUserCredentials.password).flush(<CreateTokenResponse>{ + token: mockToken, + userInfo: mockUserInfo + }); - expect(service.currentUserInfo).toEqual(mockUserInfo); + expect(service.currentUserInfo).toEqual(mockUserInfo); - httpController.verify(); + httpController.verify(); + + expect(mockLocalStorage.getItem(TOKEN_STORAGE_KEY)).toBe(rememberMe ? mockToken : null); + } + } - expect(mockLocalStorage.getItem(TOKEN_STORAGE_KEY)).toBe(mockToken); + it('remember me should work well', createTest(true)); + it('not remember me should work well', createTest(false)); }); // TODO: test on error situations. diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts index 3f6147af..6de355f2 100644 --- a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts +++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts @@ -26,6 +26,10 @@ export type SnackBarTextKey = Exclude<keyof typeof snackBarText, 'ok'>; export const TOKEN_STORAGE_KEY = 'token'; +export interface LoginInfo extends UserCredentials { + rememberMe: boolean; +} + /** * This service is only used internal in user module. */ @@ -103,12 +107,12 @@ export class InternalUserService { }]); } - tryLogin(credentials: UserCredentials, options: { remember: boolean } = { remember: true }): Observable<UserInfo> { + tryLogin(info: LoginInfo): Observable<UserInfo> { if (this.token) { return throwError(new AlreadyLoginError()); } - return this.httpClient.post<CreateTokenResponse>(createTokenUrl, <CreateTokenRequest>credentials).pipe( + return this.httpClient.post<CreateTokenResponse>(createTokenUrl, <CreateTokenRequest>info).pipe( catchError((error: HttpErrorResponse) => { if (error.error instanceof ErrorEvent) { console.error('An error occurred when login: ' + error.error.message); @@ -123,7 +127,7 @@ export class InternalUserService { }), map(result => { this.token = result.token; - if (options.remember) { + if (info.rememberMe) { this.window.localStorage.setItem(TOKEN_STORAGE_KEY, result.token); } this.userInfoSubject.next(result.userInfo); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.html b/Timeline/ClientApp/src/app/user/user-login/user-login.component.html index b1dd289d..7398ece7 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.html +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.html @@ -1,8 +1,8 @@ <form [formGroup]="form"> <ng-container *ngIf="message" [ngSwitch]="message"> - <p *ngSwitchCase="'nologin'" class="mat-body no-login-message">You haven't login.</p> - <p *ngSwitchCase="'invalidlogin'" class="mat-body invalid-login-message">Your login is no longer valid.</p> - <p *ngSwitchDefault class="mat-body error-message">{{ message }}</p> + <p *ngSwitchCase="'nologin'" class="mat-h3 no-login-message">You haven't login.</p> + <p *ngSwitchCase="'invalidlogin'" class="mat-h3 invalid-login-message">Your login is no longer valid.</p> + <p *ngSwitchDefault class="mat-h3 error-message">{{ message }}</p> </ng-container> <mat-form-field> <mat-label>Username</mat-label> @@ -13,6 +13,7 @@ <mat-label>Password</mat-label> <input formControlName="password" matInput type="password" /> </mat-form-field> + <mat-checkbox formControlName="rememberMe">Remember me!</mat-checkbox> <div class="w-100"></div> <button mat-flat-button class="login-button" (appDebounceClick)="onLoginButtonClick()">Login</button> </form> diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts index 693d5b6e..f010e4b7 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.spec.ts @@ -2,7 +2,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { ActivatedRoute } from '@angular/router'; import { of, throwError } from 'rxjs'; @@ -10,6 +9,7 @@ import { createMockInternalUserService } from '../internal-user-service/internal import { UserLoginComponent } from './user-login.component'; import { InternalUserService } from '../internal-user-service/internal-user.service'; import { UserInfo } from '../entities'; +import { MatCheckboxModule } from '@angular/material'; describe('UserLoginComponent', () => { let component: UserLoginComponent; @@ -27,7 +27,7 @@ describe('UserLoginComponent', () => { providers: [ { provide: InternalUserService, useValue: mockInternalUserService } ], - imports: [ReactiveFormsModule], + imports: [ReactiveFormsModule, MatCheckboxModule], schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); @@ -48,17 +48,20 @@ describe('UserLoginComponent', () => { const usernameInput = fixture.debugElement.query(By.css('input[type=text]')).nativeElement as HTMLInputElement; const passwordInput = fixture.debugElement.query(By.css('input[type=password]')).nativeElement as HTMLInputElement; + const rememberMeCheckbox = fixture.debugElement.query(By.css('input[type=checkbox]')).nativeElement as HTMLInputElement; usernameInput.value = 'user'; usernameInput.dispatchEvent(new Event('input')); passwordInput.value = 'user'; passwordInput.dispatchEvent(new Event('input')); + rememberMeCheckbox.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); expect(component.form.value).toEqual({ username: 'user', - password: 'user' + password: 'user', + rememberMe: true }); }); @@ -67,7 +70,8 @@ describe('UserLoginComponent', () => { const mockValue = { username: 'user', - password: 'user' + password: 'user', + rememberMe: true }; mockInternalUserService.tryLogin.withArgs(mockValue).and.returnValue(of(<UserInfo>{ username: 'user', roles: ['user'] })); @@ -84,7 +88,7 @@ describe('UserLoginComponent', () => { fixture.detectChanges(); component.message = 'nologin'; fixture.detectChanges(); - expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + expect((fixture.debugElement.query(By.css('p')).nativeElement as HTMLParagraphElement).textContent).toBe('You haven\'t login.'); }); @@ -92,7 +96,7 @@ describe('UserLoginComponent', () => { fixture.detectChanges(); component.message = 'invalidlogin'; fixture.detectChanges(); - expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + expect((fixture.debugElement.query(By.css('p')).nativeElement as HTMLParagraphElement).textContent).toBe('Your login is no longer valid.'); }); @@ -103,7 +107,8 @@ describe('UserLoginComponent', () => { const mockValue = { username: 'user', - password: 'user' + password: 'user', + rememberMe: false }; mockInternalUserService.tryLogin.withArgs(mockValue).and.returnValue(throwError(new Error(customMessage))); component.form.setValue(mockValue); @@ -111,7 +116,7 @@ describe('UserLoginComponent', () => { fixture.detectChanges(); expect(component.message).toBe(customMessage); - expect((fixture.debugElement.query(By.css('p.mat-body')).nativeElement as + expect((fixture.debugElement.query(By.css('p')).nativeElement as HTMLParagraphElement).textContent).toBe(customMessage); }); }); diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts index 836202de..4395c5cf 100644 --- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts +++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts @@ -19,7 +19,8 @@ export class UserLoginComponent implements OnInit { form = new FormGroup({ username: new FormControl(''), - password: new FormControl('') + password: new FormControl(''), + rememberMe: new FormControl(false) }); ngOnInit() { diff --git a/Timeline/ClientApp/src/app/user/user.module.ts b/Timeline/ClientApp/src/app/user/user.module.ts index 50c59662..59193380 100644 --- a/Timeline/ClientApp/src/app/user/user.module.ts +++ b/Timeline/ClientApp/src/app/user/user.module.ts @@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule } from '@angular/router'; import { MatFormFieldModule, MatProgressSpinnerModule, - MatDialogModule, MatInputModule, MatButtonModule, MatSnackBarModule + MatDialogModule, MatInputModule, MatButtonModule, MatSnackBarModule, MatCheckboxModule } from '@angular/material'; import { RequireNoLoginGuard, RequireLoginGuard } from './auth.guard'; @@ -28,7 +28,7 @@ import { UserLogoutComponent } from './user-logout/user-logout.component'; { path: '**', component: RedirectComponent, outlet: 'user' } ]), CommonModule, HttpClientModule, ReactiveFormsModule, BrowserAnimationsModule, - MatFormFieldModule, MatProgressSpinnerModule, MatDialogModule, MatInputModule, MatButtonModule, MatSnackBarModule, + MatFormFieldModule, MatProgressSpinnerModule, MatDialogModule, MatInputModule, MatButtonModule, MatCheckboxModule, MatSnackBarModule, UtilityModule ], providers: [{ provide: WINDOW, useValue: window }], |