diff --git a/src/app/components/login/i18n/en.json b/src/app/components/login/i18n/en.json index ac68120..9a09f9c 100644 --- a/src/app/components/login/i18n/en.json +++ b/src/app/components/login/i18n/en.json @@ -4,5 +4,6 @@ "LOGIN_BUTTON_LABEL": "Log in", "VALIDATION_ERRORS": { "EMPTY_VALUE": "Please, provide non-empty value" - } + }, + "INVALID_CREDS": "Login or password are incorrect" } \ No newline at end of file diff --git a/src/app/components/login/i18n/ru.json b/src/app/components/login/i18n/ru.json index 46fe596..1c028ea 100644 --- a/src/app/components/login/i18n/ru.json +++ b/src/app/components/login/i18n/ru.json @@ -4,5 +4,6 @@ "LOGIN_BUTTON_LABEL": "Войти", "VALIDATION_ERRORS": { "EMPTY_VALUE": "Пожалуйста, укажите непустое значение" - } + }, + "INVALID_CREDS": "Неверный логин или пароль" } \ No newline at end of file diff --git a/src/app/components/login/login.component.html b/src/app/components/login/login.component.html index 41e8809..16d12c8 100644 --- a/src/app/components/login/login.component.html +++ b/src/app/components/login/login.component.html @@ -1,5 +1,5 @@
-
+
+ @if(incorrectLoginOrPassword) { +
{{'login.INVALID_CREDS' | transloco}}
+ } + [disabled]="loginForm.invalid" + type="submit">{{'login.LOGIN_BUTTON_LABEL' | transloco}}
\ No newline at end of file diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index 318bc42..13ed70c 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -17,6 +17,12 @@ import { import { requiredAfterTrimValidator } from '../../validators' +import { + AuthService +} from '../../services/auth.service' +import { + HttpErrorResponse +} from '@angular/common/http' const COMPONENT_TRANSLOCO_SCOPE = 'login' @Component({ @@ -39,8 +45,12 @@ export class LoginComponent { } public loginForm - public constructor(private fb: FormBuilder) { - this.loginForm = this.fb.group({ + public incorrectLoginOrPassword = false + public constructor( + private fb: FormBuilder, + private auth$: AuthService + ) { + this.loginForm = this.fb.nonNullable.group({ login: [ '', [requiredAfterTrimValidator()] @@ -53,4 +63,21 @@ export class LoginComponent { updateOn: 'blur' }) } + + public onLogin(): void { + this.incorrectLoginOrPassword = false + this.auth$.login(this.loginForm.controls.login.value, this.loginForm.controls.password.value).subscribe({ + next: () => { + // redirect to home + }, + error: (error: HttpErrorResponse) => { + if (error.status === 403) { + this.incorrectLoginOrPassword = true + } + else { + // notify about the error + } + } + }) + } } diff --git a/src/app/components/login/test/login.component.harness.ts b/src/app/components/login/test/login.component.harness.ts index 8c1fca9..63f1b56 100644 --- a/src/app/components/login/test/login.component.harness.ts +++ b/src/app/components/login/test/login.component.harness.ts @@ -10,8 +10,8 @@ export class LoginHarness extends ComponentHarness { return await this.locatorFor(`input[data-id="${id}"]`)() } - private async getButton(id: string): Promise { - return await this.locatorFor(`button[data-id="${id}"]`)() + private async getButton(id: string, ignoreDisabled: boolean = true): Promise { + return await this.locatorFor(`button[data-id="${id}"]${ignoreDisabled ? '' : ':not([disabled])'}`)() } private async getDiv(id: string, optional: boolean = false): Promise { @@ -32,6 +32,11 @@ export class LoginHarness extends ComponentHarness { } } + public async clickButton(id: string): Promise { + const button = await this.getButton(id, false) + await button.click() + } + public async buttonEnabled(id: string): Promise { const button = await this.getButton(id) return !(await button.getProperty('disabled')) diff --git a/src/app/components/login/test/login.component.spec.ts b/src/app/components/login/test/login.component.spec.ts index 7fc23af..a2044e6 100644 --- a/src/app/components/login/test/login.component.spec.ts +++ b/src/app/components/login/test/login.component.spec.ts @@ -15,16 +15,42 @@ import { import { LoginHarness } from './login.component.harness' +import { + AuthService +} from '../../../services/auth.service' +import { + of, + throwError +} from 'rxjs' +import { + HttpErrorResponse +} from '@angular/common/http' describe('LoginComponent', () => { let loginHarness: LoginHarness + const INVALID_LOGIN = 'invalid_login' beforeEach(async () => { + const authServiceMock = jasmine.createSpyObj('authService', ['login']) + authServiceMock.login.and.callFake((login: string) => { + if (login === INVALID_LOGIN) { + return throwError(() => new HttpErrorResponse({ + status: 403 + })) + } + else { + return of() + } + }) await TestBed.configureTestingModule({ imports: [ LoginComponent, getTranslocoTestingModule(LoginComponent, en) - ] + ], + providers: [{ + provide: AuthService, + useValue: authServiceMock + }] }).compileComponents() const fixture = TestBed.createComponent(LoginComponent) @@ -94,4 +120,13 @@ describe('LoginComponent', () => { await loginHarness.enterInputValue('passwordInput', ' ') expect(await loginHarness.controlPresent('passwordErrorEmpty')).toBe(false) }) + + it('should display error message in case of invalid credentials', async () => { + expect(await loginHarness.controlPresent('incorrectCreds')).toBe(false) + + await loginHarness.enterInputValue('loginInput', INVALID_LOGIN) + await loginHarness.enterInputValue('passwordInput', 'my_password') + await loginHarness.clickButton('loginButton') + expect(await loginHarness.controlPresent('incorrectCreds')).toBe(true) + }) }) diff --git a/src/app/services/auth.service.spec.ts b/src/app/services/auth.service.spec.ts new file mode 100644 index 0000000..4999bba --- /dev/null +++ b/src/app/services/auth.service.spec.ts @@ -0,0 +1,20 @@ +import { + TestBed +} from '@angular/core/testing' + +import { + AuthService +} from './auth.service' + +describe('AuthService', () => { + let service: AuthService + + beforeEach(() => { + TestBed.configureTestingModule({}) + service = TestBed.inject(AuthService) + }) + + it('should be created', () => { + expect(service).toBeTruthy() + }) +}) diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..b6fbc48 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,19 @@ +import { + Injectable +} from '@angular/core' +import { + Observable, + of +} from 'rxjs' + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + public constructor() { } + + public login(login: string, password: string): Observable { + console.log(login + ' ' + password) + return of() + } +}