diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index 44840fb1..5e29c4d0 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -54,7 +54,7 @@ - +
diff --git a/src/app/auth/auth.component.spec.ts b/src/app/auth/auth.component.spec.ts new file mode 100644 index 00000000..5fb22ff6 --- /dev/null +++ b/src/app/auth/auth.component.spec.ts @@ -0,0 +1,119 @@ +import { asyncScheduler, observeOn, of, throwError } from 'rxjs'; + +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { By } from '@angular/platform-browser'; + +import { AuthService } from '@services/access/auth.service'; +import { AlertService } from '@services/shared/alert.service'; +import { ConfigService } from '@services/shared/config.service'; +import { UnsubscribeService } from '@services/unsubscribe.service'; + +import { AuthComponent } from '@src/app/auth/auth.component'; +import { ButtonsModule } from '@src/app/shared/buttons/buttons.module'; +import { ComponentsModule } from '@src/app/shared/components.module'; +import { InputModule } from '@src/app/shared/input/input.module'; + +describe('AuthComponent', () => { + let component: AuthComponent; + let fixture: ComponentFixture; + + // Mocks + let mockAuthService: jasmine.SpyObj; + let mockAlertService: jasmine.SpyObj; + let mockUnsubscribeService: jasmine.SpyObj; + let mockConfigService: jasmine.SpyObj; + + beforeEach(async () => { + mockAuthService = jasmine.createSpyObj('AuthService', ['logIn']); + mockAlertService = jasmine.createSpyObj('AlertService', ['showErrorMessage']); + mockUnsubscribeService = jasmine.createSpyObj('UnsubscribeService', ['add', 'unsubscribeAll']); + mockConfigService = jasmine.createSpyObj('ConfigService', ['getEndpoint']); + + await TestBed.configureTestingModule({ + imports: [ + ReactiveFormsModule, + MatCardModule, + MatFormFieldModule, + MatInputModule, + MatIconModule, + MatButtonModule, + InputModule, + ButtonsModule, + ComponentsModule, + MatProgressSpinnerModule + ], + declarations: [AuthComponent], + providers: [ + { provide: AuthService, useValue: mockAuthService }, + { provide: AlertService, useValue: mockAlertService }, + { provide: UnsubscribeService, useValue: mockUnsubscribeService }, + { provide: ConfigService, useValue: mockConfigService } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(AuthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create the component and initialize form', () => { + expect(component).toBeTruthy(); + expect(component.loginForm).toBeTruthy(); + expect(component.loginForm.controls['username']).toBeTruthy(); + expect(component.loginForm.controls['password']).toBeTruthy(); + }); + + it('should mark form invalid if empty', () => { + component.loginForm.setValue({ username: '', password: '' }); + expect(component.loginForm.invalid).toBeTrue(); + }); + + it('should call authService.logIn on valid form submission via button click', fakeAsync(() => { + const fakeResponse = of({ token: '123' }).pipe(observeOn(asyncScheduler)); + mockAuthService.logIn.and.returnValue(fakeResponse); + component.loginForm.setValue({ username: 'user', password: 'pass' }); + + const submitButtonDebugEl = fixture.debugElement.query(By.css('button-submit button')); + submitButtonDebugEl.nativeElement.click(); + + expect(component.isLoading).toBeTrue(); + expect(mockAuthService.logIn).toHaveBeenCalledWith('user', 'pass'); + + tick(); + + expect(component.isLoading).toBeFalse(); + expect(component.loginForm.value.username).toBeNull(); + expect(component.loginForm.value.password).toBeNull(); + expect(mockUnsubscribeService.add).toHaveBeenCalled(); + })); + + it('should show error message when login fails on submit button click', fakeAsync(() => { + const errorResponse = throwError(() => new Error('Login failed')).pipe(observeOn(asyncScheduler)); + mockAuthService.logIn.and.returnValue(errorResponse); + + component.loginForm.setValue({ username: 'user', password: 'wrong' }); + + const submitButtonDebugEl = fixture.debugElement.query(By.css('button-submit button')); + submitButtonDebugEl.nativeElement.click(); + + expect(component.isLoading).toBeTrue(); + expect(mockAuthService.logIn).toHaveBeenCalledWith('user', 'wrong'); + + tick(); + + expect(component.isLoading).toBeFalse(); + expect(mockAlertService.showErrorMessage).toHaveBeenCalled(); + })); + + it('should call unsubscribeAll on destroy', () => { + component.ngOnDestroy(); + expect(mockUnsubscribeService.unsubscribeAll).toHaveBeenCalled(); + }); +}); diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index 3f80d692..028d6129 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -15,8 +15,8 @@ import { LocalStorageService } from '@services/storage/local-storage.service'; import { UnsubscribeService } from '@services/unsubscribe.service'; import { UISettingsUtilityClass } from '@src/app/shared/utils/config'; -import { environment } from '@src/environments/environment'; import { HeaderConfig } from '@src/config/default/app/config.model'; +import { environment } from '@src/environments/environment'; @Component({ selector: 'app-login',