diff --git a/client/src/app/core/containers/app.component.spec.ts b/client/src/app/core/containers/app.component.spec.ts index ad205840c6623e5ecf5d50c8abb035939390e6f5..548bcf07d1f8fe03b82ea3d2ee1a1bd21076bd5b 100644 --- a/client/src/app/core/containers/app.component.spec.ts +++ b/client/src/app/core/containers/app.component.spec.ts @@ -7,7 +7,6 @@ import { provideMockStore, MockStore } from '@ngrx/store/testing'; import { AppComponent } from './app.component'; import { AppConfigService } from 'src/app/app-config.service'; import * as authActions from 'src/app/auth/auth.actions'; -import * as attributeActions from '../../metamodel/actions/attribute.actions'; import * as instanceActions from '../../metamodel/actions/instance.actions'; describe('AppComponent', () => { diff --git a/client/src/app/portal/components/instance-card.component.html b/client/src/app/portal/components/instance-card.component.html index 85c5825eb2dcd9d7c2888f9c548fb3ed9a7db456..2916864f55780973f006196bb77f59c6c89c1eb7 100644 --- a/client/src/app/portal/components/instance-card.component.html +++ b/client/src/app/portal/components/instance-card.component.html @@ -4,7 +4,7 @@ <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p> </div> <div class="card-footer bg-transparent text-right"> - <a routerLink="/instance/{{instance.name}}" class="btn btn-outline-primary" title="Go to instance"> + <a routerLink="/instance/{{ instance.name }}" class="btn btn-outline-primary" title="Go to instance"> Go to instance </a> </div> diff --git a/client/src/app/portal/components/instance-card.component.spec.ts b/client/src/app/portal/components/instance-card.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ce3bf87334a972173dd70338bef17a4fb0fc202 --- /dev/null +++ b/client/src/app/portal/components/instance-card.component.spec.ts @@ -0,0 +1,22 @@ +import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { InstanceCardComponent } from './instance-card.component'; + +describe('InstanceCardComponent', () => { + let component: InstanceCardComponent; + let fixture: ComponentFixture<InstanceCardComponent>; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [InstanceCardComponent] + }).compileComponents(); + fixture = TestBed.createComponent(InstanceCardComponent); + component = fixture.componentInstance; + })); + + it('should create the component', () => { + expect(component).toBeDefined(); + }); +}); diff --git a/client/src/app/portal/components/instance-card.component.ts b/client/src/app/portal/components/instance-card.component.ts index 405a808a5ab29f76cf4855b6fffc904c6cc3efad..c50e4b734d74a0e6c02d73430db6a8bedeb4af01 100644 --- a/client/src/app/portal/components/instance-card.component.ts +++ b/client/src/app/portal/components/instance-card.component.ts @@ -11,6 +11,10 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { Instance } from 'src/app/metamodel/models'; +/** + * @class + * @classdesc Instance card component. + */ @Component({ selector: 'app-instance-card', templateUrl: 'instance-card.component.html', diff --git a/client/src/app/portal/containers/portal-home.component.spec.ts b/client/src/app/portal/containers/portal-home.component.spec.ts index 6d707e36f09a66596196c426ec4fc632bcbf1ddd..e7e405cbf2d0ba90f1a60e9f8ee896b3f11ecc02 100644 --- a/client/src/app/portal/containers/portal-home.component.spec.ts +++ b/client/src/app/portal/containers/portal-home.component.spec.ts @@ -3,17 +3,18 @@ import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing' import { RouterTestingModule } from '@angular/router/testing'; import { provideMockStore, MockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { PortalHomeComponent } from './portal-home.component'; import { AppConfigService } from 'src/app/app-config.service'; import { UserProfile } from 'src/app/auth/user-profile.model'; import { Instance } from 'src/app/metamodel/models'; import * as authActions from 'src/app/auth/auth.actions'; -import { PortalHomeComponent } from './portal-home.component'; describe('PortalHomeComponent', () => { @Component({ selector: 'app-navbar', template: '' }) class NavbarStubComponent { - @Input() links: {label: string, icon: string, routerLink: string}[]; + @Input() links: { label: string, icon: string, routerLink: string }[]; @Input() isAuthenticated: boolean; @Input() userProfile: UserProfile = null; @Input() baseHref: string; @@ -28,13 +29,11 @@ describe('PortalHomeComponent', () => { let component: PortalHomeComponent; let fixture: ComponentFixture<PortalHomeComponent>; let store: MockStore; - let config: AppConfigService + let appConfigServiceStub = new AppConfigService(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], + imports: [RouterTestingModule], declarations: [ PortalHomeComponent, NavbarStubComponent, @@ -42,47 +41,81 @@ describe('PortalHomeComponent', () => { ], providers: [ provideMockStore({ }), - AppConfigService + { provide: AppConfigService, useValue: appConfigServiceStub } ] }).compileComponents(); fixture = TestBed.createComponent(PortalHomeComponent); component = fixture.componentInstance; store = TestBed.inject(MockStore); - config = TestBed.inject(AppConfigService); + document.body.innerHTML = '<link id="favicon" href="">'; })); - it('should create the portal home component', () => { + it('should create the component', () => { expect(component).toBeDefined(); }); - it('getBaseHref() should give base href config key value', () => { - config.baseHref = '/my-project'; + it('should execute ngOnInit lifecycle and add admin link if no authentication', () => { + appConfigServiceStub.authenticationEnabled = false; + component.ngOnInit(); + const expected = [ + { label: 'Home', icon: 'fas fa-home', routerLink: '/portal' }, + { label: 'Admin', icon: 'fas fa-tools', routerLink: '/admin' } + ]; + expect(component.links).toEqual(expected); + expect(component.favIcon.href).toEqual('http://localhost/favicon.ico'); + }); + + it('should execute ngOnInit lifecycle and add admin link depending on user rights', () => { + appConfigServiceStub.authenticationEnabled = true; + appConfigServiceStub.adminRole = 'admin'; + component.userRoles = of([]); + component.ngOnInit(); + expect(component.links).toEqual([{ label: 'Home', icon: 'fas fa-home', routerLink: '/portal' }]); + component.userRoles = of(['admin']); + component.ngOnInit(); + const expected = [ + { label: 'Home', icon: 'fas fa-home', routerLink: '/portal' }, + { label: 'Admin', icon: 'fas fa-tools', routerLink: '/admin' } + ]; + expect(component.links).toEqual(expected); + + }); + + it('#getBaseHref() should return base href config key value', () => { + appConfigServiceStub.baseHref = '/my-project'; expect(component.getBaseHref()).toBe('/my-project'); }); - it('authenticationEnabled() should give authentication enabled config key value', () => { - config.authenticationEnabled = true; + it('#authenticationEnabled() should return authentication enabled config key value', () => { + appConfigServiceStub.authenticationEnabled = true; expect(component.getAuthenticationEnabled()).toBeTruthy(); }); - it('login() should dispatch login action', () => { + it('#login() should dispatch login action', () => { const spy = jest.spyOn(store, 'dispatch'); component.login(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(authActions.login()); }); - it('logout() should dispatch logout action', () => { + it('#logout() should dispatch logout action', () => { const spy = jest.spyOn(store, 'dispatch'); component.logout(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(authActions.logout()); }); - it('openEditProfile() should dispatch open edit profile action', () => { + it('#openEditProfile() should dispatch open edit profile action', () => { const spy = jest.spyOn(store, 'dispatch'); component.openEditProfile(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(authActions.openEditProfile()); }); + + it('should unsubscribe to user roles when component is destroyed', () => { + component.userRolesSubscription = of().subscribe(); + const spy = jest.spyOn(component.userRolesSubscription, 'unsubscribe'); + component.ngOnDestroy(); + expect(spy).toHaveBeenCalledTimes(1); + }); }); diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts index e64f8f617dc3ddf6a1b8acac4b709fb28c527a08..5a7e3ae9eb65d592850815589e008bffd20c4f9c 100644 --- a/client/src/app/portal/containers/portal-home.component.ts +++ b/client/src/app/portal/containers/portal-home.component.ts @@ -8,6 +8,7 @@ */ import { Component, OnInit, OnDestroy } from '@angular/core'; + import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; @@ -18,26 +19,24 @@ import * as authSelector from 'src/app/auth/auth.selector'; import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; import { AppConfigService } from 'src/app/app-config.service'; -@Component({ - selector: 'app-portal-home', - templateUrl: 'portal-home.component.html' -}) /** * @class * @classdesc Portal home container. * * @implements OnInit + * @implements OnDestroy */ +@Component({ + selector: 'app-portal-home', + templateUrl: 'portal-home.component.html' +}) export class PortalHomeComponent implements OnInit, OnDestroy { public favIcon: HTMLLinkElement = document.querySelector('#favicon'); - public links = [ - { label: 'Home', icon: 'fas fa-home', routerLink: '/portal' } - ]; + public links = [{ label: 'Home', icon: 'fas fa-home', routerLink: '/portal' }]; public isAuthenticated: Observable<boolean>; public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; public instanceList: Observable<Instance[]>; - public userRolesSubscription: Subscription; constructor(private store: Store<{ }>, private config: AppConfigService) { @@ -61,27 +60,49 @@ export class PortalHomeComponent implements OnInit, OnDestroy { } } - getBaseHref() { + /** + * Returns application base href. + * + * @return string + */ + getBaseHref(): string { return this.config.baseHref; } - getAuthenticationEnabled() { + /** + * Checks if authentication is enabled. + * + * @return boolean + */ + getAuthenticationEnabled(): boolean { return this.config.authenticationEnabled; } - + + /** + * Dispatches action to log in. + */ login(): void { this.store.dispatch(authActions.login()); } + /** + * Dispatches action to log out. + */ logout(): void { this.store.dispatch(authActions.logout()); } + /** + * Dispatches action to open profile editor. + */ openEditProfile(): void { this.store.dispatch(authActions.openEditProfile()); } - ngOnDestroy() { + /** + * Unsubscribes to user roles when component is destroyed. + */ + ngOnDestroy(): void { if (this.userRolesSubscription) this.userRolesSubscription.unsubscribe(); } } diff --git a/client/src/app/portal/portal-routing.module.ts b/client/src/app/portal/portal-routing.module.ts index 3874af69e7b07bd30358857ec40196b68681f2f5..dc43b6ed26cc553539c67fcac8ab8758e647d235 100644 --- a/client/src/app/portal/portal-routing.module.ts +++ b/client/src/app/portal/portal-routing.module.ts @@ -17,6 +17,10 @@ const routes: Routes = [ { path: 'home', component: PortalHomeComponent } ]; +/** + * @class + * @classdesc Portal routing module. + */ @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] diff --git a/client/src/app/portal/portal.module.ts b/client/src/app/portal/portal.module.ts index 09687afe56a281b70254d7ab38b2365bb1b7c755..90d8e5a01cc4593a5af19e01c7025e33bf021ad6 100644 --- a/client/src/app/portal/portal.module.ts +++ b/client/src/app/portal/portal.module.ts @@ -13,6 +13,10 @@ import { SharedModule } from 'src/app/shared/shared.module'; import { PortalRoutingModule, routedComponents } from './portal-routing.module'; import { dummiesComponents } from './components'; +/** + * @class + * @classdesc Portal module. + */ @NgModule({ imports: [ SharedModule,