diff --git a/CHANGELOG.md b/CHANGELOG.md index a65dc11..4a7e19f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Made predefined commands read-only to prevent accidental modifications. - Simplified the task creation modal to enhance user experience. - Adjusted the translation system to cover new elements and improve consistency (work in progress). +- New element view from clients on groups main view ### Fixed - Resolved an issue that prevented editing software profiles correctly. diff --git a/ogWebconsole/src/app/components/admin/admin.component.spec.ts b/ogWebconsole/src/app/components/admin/admin.component.spec.ts index b3b4abc..5bd5fe2 100644 --- a/ogWebconsole/src/app/components/admin/admin.component.spec.ts +++ b/ogWebconsole/src/app/components/admin/admin.component.spec.ts @@ -3,11 +3,13 @@ import { RouterTestingModule } from '@angular/router/testing'; import { AdminComponent } from './admin.component'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { TranslateModule } from '@ngx-translate/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { Router } from '@angular/router'; describe('AdminComponent', () => { let component: AdminComponent; let fixture: ComponentFixture; + let router: Router; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -16,9 +18,11 @@ describe('AdminComponent', () => { RouterTestingModule, MatButtonModule, MatIconModule, - TranslateModule.forRoot() + TranslateModule.forRoot() ] }).compileComponents(); + + router = TestBed.inject(Router); }); beforeEach(() => { @@ -30,4 +34,24 @@ describe('AdminComponent', () => { it('debería crear el componente', () => { expect(component).toBeTruthy(); }); + + it('debería renderizar dos botones', () => { + const buttons = fixture.nativeElement.querySelectorAll('button'); + expect(buttons.length).toBe(2); + }); + + it('debería tener un botón con routerLink a "/users"', () => { + const button = fixture.nativeElement.querySelector('button[routerLink="/users"]'); + expect(button).toBeTruthy(); + expect(button.querySelector('mat-icon').textContent.trim()).toBe('group'); + }); + + + it('debería aplicar la clase "fab-button" a ambos botones', () => { + const buttons = fixture.nativeElement.querySelectorAll('button'); + buttons.forEach((button: HTMLElement) => { + expect(button.classList).toContain('fab-button'); + }); + }); + }); diff --git a/ogWebconsole/src/app/components/admin/roles/roles/roles.component.spec.ts b/ogWebconsole/src/app/components/admin/roles/roles/roles.component.spec.ts index 803c3c1..b78ca46 100644 --- a/ogWebconsole/src/app/components/admin/roles/roles/roles.component.spec.ts +++ b/ogWebconsole/src/app/components/admin/roles/roles/roles.component.spec.ts @@ -51,4 +51,18 @@ describe('RolesComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have a default itemsPerPage value', () => { + expect(component.itemsPerPage).toBeDefined(); + }); + + it('should initialize the dataSource', () => { + expect(component.dataSource).toBeDefined(); + }); + + it('should have a defined columns array', () => { + expect(component.columns).toBeDefined(); + expect(component.columns.length).toBeGreaterThan(0); + }); + }); diff --git a/ogWebconsole/src/app/components/admin/users/users/users.component.spec.ts b/ogWebconsole/src/app/components/admin/users/users/users.component.spec.ts index bb390d4..ba8906d 100644 --- a/ogWebconsole/src/app/components/admin/users/users/users.component.spec.ts +++ b/ogWebconsole/src/app/components/admin/users/users/users.component.spec.ts @@ -8,15 +8,9 @@ import { of } from 'rxjs'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; -class MockUserService { - getUsers() { - return of({ - 'hydra:member': [ - { id: 1, username: 'user1', allowedOrganizationalUnits: [], roles: ['admin'] }, - { id: 2, username: 'user2', allowedOrganizationalUnits: [], roles: ['user'] } - ] - }); - } +class MockToastrService { + success() {} + error() {} } describe('UsersComponent', () => { @@ -30,15 +24,13 @@ describe('UsersComponent', () => { MatTableModule, MatDialogModule, HttpClientTestingModule, - TranslateModule.forRoot() + TranslateModule.forRoot(), ], providers: [ - { useClass: MockUserService }, - { provide: ToastrService, useValue: { success: () => {} } }, + { provide: ToastrService, useClass: MockToastrService }, ], - schemas: [NO_ERRORS_SCHEMA], - }) - .compileComponents(); + schemas: [NO_ERRORS_SCHEMA], // Ignorar elementos desconocidos + }).compileComponents(); fixture = TestBed.createComponent(UsersComponent); component = fixture.componentInstance; @@ -49,4 +41,27 @@ describe('UsersComponent', () => { expect(component).toBeTruthy(); }); + it('should have default values for pagination', () => { + expect(component.itemsPerPage).toBe(10); + expect(component.page).toBe(0); + expect(component.pageSizeOptions).toEqual([5, 10, 20, 40, 100]); + }); + + it('should call search on init', () => { + spyOn(component, 'search'); + component.ngOnInit(); + expect(component.search).toHaveBeenCalled(); + }); + + it('should initialize the dataSource', () => { + expect(component.dataSource).toBeDefined(); + }); + + it('should define displayedColumns', () => { + expect(component.displayedColumns).toBeDefined(); + expect(component.displayedColumns).toContain('id'); + expect(component.displayedColumns).toContain('username'); + expect(component.displayedColumns).toContain('roles'); + expect(component.displayedColumns).toContain('actions'); + }); }); diff --git a/ogWebconsole/src/app/components/login/login.component.spec.ts b/ogWebconsole/src/app/components/login/login.component.spec.ts index 499e390..86ecb69 100644 --- a/ogWebconsole/src/app/components/login/login.component.spec.ts +++ b/ogWebconsole/src/app/components/login/login.component.spec.ts @@ -8,6 +8,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatIconModule } from '@angular/material/icon'; import { TranslateModule } from '@ngx-translate/core'; +import { of, throwError } from 'rxjs'; describe('LoginComponent', () => { let component: LoginComponent; @@ -18,17 +19,16 @@ describe('LoginComponent', () => { declarations: [LoginComponent], imports: [ FormsModule, - ToastrModule.forRoot(), - BrowserAnimationsModule, - MatFormFieldModule, - MatInputModule, + ToastrModule.forRoot(), + BrowserAnimationsModule, + MatFormFieldModule, + MatInputModule, MatIconModule, - TranslateModule.forRoot() + TranslateModule.forRoot() ], providers: [provideHttpClient(withInterceptorsFromDi())] - }) - .compileComponents(); - + }).compileComponents(); + fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -37,4 +37,101 @@ describe('LoginComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should disable the login button if username or password is missing', () => { + component.loginObj.username = ''; + component.loginObj.password = ''; + fixture.detectChanges(); + const button = fixture.nativeElement.querySelector('button[type="submit"]'); + expect(button.disabled).toBeTruthy(); + }); + + it('should enable the login button if username and password are present', () => { + component.loginObj.username = 'testUser'; + component.loginObj.password = 'testPass'; + fixture.detectChanges(); + const button = fixture.nativeElement.querySelector('button[type="submit"]'); + expect(button.disabled).toBeFalsy(); + }); + + it('should call onLogin and navigate on successful login', () => { + const mockRouter = spyOn(component['router'], 'navigateByUrl'); + const mockHttp = spyOn(component['http'], 'post').and.returnValue(of({ token: '123', refreshToken: '456' })); + + component.loginObj.username = 'testUser'; + component.loginObj.password = 'testPass'; + component.onLogin(); + + expect(mockHttp).toHaveBeenCalledWith(`${component.baseUrl}/auth/login`, component.loginObj); + expect(mockRouter).toHaveBeenCalledWith('/groups'); + }); + + it('should show error message on login failure', () => { + const mockToast = spyOn(component['toastService'], 'error'); + const mockHttp = spyOn(component['http'], 'post').and.returnValue(throwError({ error: { message: 'Invalid credentials' } })); + + component.loginObj.username = 'testUser'; + component.loginObj.password = 'testPass'; + component.onLogin(); + + expect(mockHttp).toHaveBeenCalled(); + expect(mockToast).toHaveBeenCalledWith('Error al iniciar sesión: Invalid credentials', 'Error'); + }); + + it('should change language to Spanish and save to localStorage', () => { + const mockTranslate = spyOn(component['translateService'], 'use'); + component.changeLanguage('es'); + expect(localStorage.getItem('language')).toBe('es'); + expect(mockTranslate).toHaveBeenCalledWith('es'); + }); + + it('should change language to English and save to localStorage', () => { + const mockTranslate = spyOn(component['translateService'], 'use'); + component.changeLanguage('en'); + expect(localStorage.getItem('language')).toBe('en'); + expect(mockTranslate).toHaveBeenCalledWith('en'); + }); + + it('should toggle password visibility when clicking the button', () => { + const initialHideState = component.hide(); + const button = fixture.nativeElement.querySelector('button[mat-icon-button]'); + button.click(); + expect(component.hide()).toBe(!initialHideState); + }); + + it('should add "invalid" class to username input if it is invalid and touched', () => { + const usernameInput = fixture.nativeElement.querySelector('input[name="username"]'); + usernameInput.value = ''; // Empty value makes it invalid + usernameInput.dispatchEvent(new Event('input')); + usernameInput.dispatchEvent(new Event('blur')); // Simulates "touched" + fixture.detectChanges(); + + expect(usernameInput.classList).toContain('invalid'); + }); + + it('should add rotating class to the logo when loading', () => { + component.isLoading = true; + fixture.detectChanges(); + const logo = fixture.nativeElement.querySelector('.login-logo'); + expect(logo.classList).toContain('rotating'); + }); + + it('should not add rotating class to the logo when not loading', () => { + component.isLoading = false; + fixture.detectChanges(); + const logo = fixture.nativeElement.querySelector('.login-logo'); + expect(logo.classList).not.toContain('rotating'); + }); + + it('should show a success toast message', () => { + const mockToast = spyOn(component['toastService'], 'success'); + component.openSnackBar(false, 'Welcome!'); + expect(mockToast).toHaveBeenCalledWith('Welcome!', 'Éxito'); + }); + + it('should show an error toast message', () => { + const mockToast = spyOn(component['toastService'], 'error'); + component.openSnackBar(true, 'Something went wrong'); + expect(mockToast).toHaveBeenCalledWith('Something went wrong', 'Error'); + }); }); diff --git a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.spec.ts b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.spec.ts index a2e455e..41e09b6 100644 --- a/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.spec.ts +++ b/ogWebconsole/src/app/components/ogboot/pxe/pxe.component.spec.ts @@ -69,4 +69,64 @@ describe('PxeComponent', () => { it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should have a defined component', () => { + expect(component).toBeDefined(); + }); + + it('should have a defined baseUrl', () => { + expect(component.baseUrl).toBeDefined(); + }); + + it('should have a defined pxeTemplates', () => { + expect(component.pxeTemplates).toBeDefined(); + }); + + it('should have a defined currentPage', () => { + expect(component.currentPage).toBeDefined(); + }); + + it('should have a defined dataSource', () => { + expect(component.dataSource).toBeDefined(); + }); + + it('should have a defined length', () => { + expect(component.length).toBeDefined(); + }); + + it('should have a defined itemsPerPage', () => { + expect(component.itemsPerPage).toBeDefined(); + }); + + it('should have a defined page', () => { + expect(component.page).toBeDefined(); + }); + + it('should have a defined pageSizeOptions', () => { + expect(component.pageSizeOptions).toBeDefined(); + }); + + it('should have a defined selectedElements', () => { + expect(component.selectedElements).toBeDefined(); + }); + + it('should have a defined loading', () => { + expect(component.loading).toBeDefined(); + }); + + it('should have a defined filters', () => { + expect(component.filters).toBeDefined(); + }); + + it('should have a defined alertMessage', () => { + expect(component.alertMessage).toBeDefined(); + }); + + it('should have a defined datePipe', () => { + expect(component.datePipe).toBeDefined(); + }); + + it('should have a defined selectedItem', () => { + expect(component.selectedItem).toBeDefined(); + }); });