diff --git a/ui/src/app/admin/action/admin-collection.action.ts b/ui/src/app/admin/action/admin-collection.action.ts index 96c2ae937..f9dacfd34 100644 --- a/ui/src/app/admin/action/admin-collection.action.ts +++ b/ui/src/app/admin/action/admin-collection.action.ts @@ -3,9 +3,6 @@ import { Update } from '@ngrx/entity'; import { Admin } from '../model/admin'; export enum AdminCollectionActionTypes { - SELECT_ADMIN_REQUEST = '[Admin Collection] Select Admin Request', - SELECT_ADMIN_SUCCESS = '[Admin Collection] Select Admin Success', - SELECT_ADMIN_FAIL = '[Admin Collection] Select Admin Fail', UPDATE_ADMIN_REQUEST = '[Admin Collection] Update Admin Request', UPDATE_ADMIN_SUCCESS = '[Admin Collection] Update Admin Success', @@ -27,24 +24,6 @@ export enum AdminCollectionActionTypes { } -export class SelectAdmin implements Action { - readonly type = AdminCollectionActionTypes.SELECT_ADMIN_REQUEST; - - constructor(public payload: string) { } -} - -export class SelectAdminSuccess implements Action { - readonly type = AdminCollectionActionTypes.SELECT_ADMIN_SUCCESS; - - constructor(public payload: Admin) { } -} - -export class SelectAdminFail implements Action { - readonly type = AdminCollectionActionTypes.SELECT_ADMIN_FAIL; - - constructor(public payload: Error) { } -} - export class LoadAdminRequest implements Action { readonly type = AdminCollectionActionTypes.LOAD_ADMIN_REQUEST; @@ -132,9 +111,6 @@ export type AdminCollectionActionsUnion = | RemoveAdminRequest | RemoveAdminSuccess | RemoveAdminFail - | SelectAdmin - | SelectAdminSuccess - | SelectAdminFail | UpdateAdminRequest | UpdateAdminSuccess | UpdateAdminFail diff --git a/ui/src/app/admin/component/access-request.component.spec.ts b/ui/src/app/admin/component/access-request.component.spec.ts new file mode 100644 index 000000000..5c236e63c --- /dev/null +++ b/ui/src/app/admin/component/access-request.component.spec.ts @@ -0,0 +1,65 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Store, StoreModule, combineReducers } from '@ngrx/store'; +import { FormsModule } from '@angular/forms'; + +import { NgbModalStub } from '../../../testing/modal.stub'; +import { AccessRequestComponent } from './access-request.component'; +import * as fromAdmin from '../reducer'; +import * as fromCore from '../../core/reducer'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(AccessRequestComponent, { static: true }) + public componentUnderTest: AccessRequestComponent; +} + +describe('Access Request Component', () => { + + let app: AccessRequestComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + NgbDropdownModule, + RouterTestingModule, + StoreModule.forRoot({ + admin: combineReducers(fromAdmin.reducers), + core: combineReducers(fromCore.reducers) + }) + ], + declarations: [ + AccessRequestComponent, + TestHostComponent + ], + providers: [ + { + provide: NgbModal, + useClass: NgbModalStub + } + ] + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/admin/component/delete-user-dialog.component.spec.ts b/ui/src/app/admin/component/delete-user-dialog.component.spec.ts new file mode 100644 index 000000000..50acb6d54 --- /dev/null +++ b/ui/src/app/admin/component/delete-user-dialog.component.spec.ts @@ -0,0 +1,55 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbDropdownModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { DeleteUserDialogComponent } from './delete-user-dialog.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(DeleteUserDialogComponent, { static: true }) + public componentUnderTest: DeleteUserDialogComponent; +} + +describe('Delete Dialog (modal) Component', () => { + + let app: DeleteUserDialogComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + RouterTestingModule + ], + declarations: [ + DeleteUserDialogComponent, + TestHostComponent + ], + providers: [ + { + provide: NgbActiveModal, + useValue: jasmine.createSpyObj('activeModal', [ + 'close', + 'dismiss' + ]) + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html index 7d3558e09..dfe16220c 100644 --- a/ui/src/app/admin/component/enable-metadata.component.html +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -2,20 +2,3 @@ [entities]="resolvers$ | async" (delete)="deleteResolver($event)" (toggleEnabled)="toggleResolverEnabled($event)"> - - \ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.spec.ts b/ui/src/app/admin/component/enable-metadata.component.spec.ts new file mode 100644 index 000000000..3d3132edb --- /dev/null +++ b/ui/src/app/admin/component/enable-metadata.component.spec.ts @@ -0,0 +1,65 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbDropdownModule, NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { EnableMetadataComponent } from './enable-metadata.component'; + +import * as fromAdmin from '../reducer'; +import { Store, StoreModule, combineReducers } from '@ngrx/store'; +import { NgbModalStub } from '../../../testing/modal.stub'; +import { MockResolversListComponent } from '../../../testing/resolvers-list.component.stub'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(EnableMetadataComponent, { static: true }) + public componentUnderTest: EnableMetadataComponent; +} + +describe('Enable Metadata (modal) Component', () => { + + let app: EnableMetadataComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + RouterTestingModule, + StoreModule.forRoot({ + admin: combineReducers(fromAdmin.reducers) + }) + ], + declarations: [ + EnableMetadataComponent, + TestHostComponent, + MockResolversListComponent + ], + providers: [ + { + provide: NgbModal, + useClass: NgbModalStub + } + ] + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/admin/component/user-management.component.spec.ts b/ui/src/app/admin/component/user-management.component.spec.ts new file mode 100644 index 000000000..53eab38c9 --- /dev/null +++ b/ui/src/app/admin/component/user-management.component.spec.ts @@ -0,0 +1,64 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Store, StoreModule, combineReducers } from '@ngrx/store'; +import { NgbDropdownModule, NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; +import { UserManagementComponent } from './user-management.component'; +import { NgbModalStub } from '../../../testing/modal.stub'; +import * as fromAdmin from '../reducer'; +import * as fromCore from '../../core/reducer'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(UserManagementComponent, { static: true }) + public componentUnderTest: UserManagementComponent; +} + +describe('User Management Component', () => { + + let app: UserManagementComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + NgbDropdownModule, + RouterTestingModule, + StoreModule.forRoot({ + admin: combineReducers(fromAdmin.reducers), + core: combineReducers(fromCore.reducers) + }) + ], + declarations: [ + UserManagementComponent, + TestHostComponent + ], + providers: [ + { + provide: NgbModal, + useClass: NgbModalStub + } + ] + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/admin/effect/admin-collection.effect.spec.ts b/ui/src/app/admin/effect/admin-collection.effect.spec.ts new file mode 100644 index 000000000..20159f7b8 --- /dev/null +++ b/ui/src/app/admin/effect/admin-collection.effect.spec.ts @@ -0,0 +1,84 @@ +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Subject, Observable, of, ReplaySubject } from 'rxjs'; + +import { AdminCollectionEffects } from './admin-collection.effect'; +import { AdminService } from '../service/admin.service'; +import { Admin } from '../model/admin'; +import { LoadAdminRequest, LoadAdminSuccess, UpdateAdminSuccess } from '../action/admin-collection.action'; +import { AddNotification, ADD_NOTIFICATION } from '../../notification/action/notification.action'; +import { NotificationType, Notification } from '../../notification/model/notification'; + +describe('Admin Collection Effects', () => { + let effects: AdminCollectionEffects; + let actions: Subject; + + let admin: Admin = { + username: 'foo', + firstName: 'bar', + lastName: 'baz', + role: 'ROLE_ADMIN', + emailAddress: 'foo@bar.baz' + }; + + let mockAdminService = { + query: (): Observable => of([admin]), + queryByRole: (role: string): Observable => of([admin]), + update: (user: Admin): Observable => of(admin), + remove: (userId: string): Observable => of(true) + }; + + let adminService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [ + { provide: AdminService, useValue: mockAdminService }, + AdminCollectionEffects, + provideMockActions(() => actions), + ], + }); + + effects = TestBed.get(AdminCollectionEffects); + adminService = TestBed.get(AdminService); + }); + + describe(`loadAdminRequest$ effect`, () => { + it('should load admins and fire a success action', () => { + spyOn(adminService, 'query').and.returnValue(of([admin])); + actions = new ReplaySubject(1); + + actions.next(new LoadAdminRequest()); + + effects.loadAdminRequest$.subscribe(result => { + expect(result).toEqual(new LoadAdminSuccess([admin])); + }); + }); + }); + + describe('updateAdminRoleReload$ effect', () => { + it('should reload the admins when the admin is updated', () => { + actions = new ReplaySubject(1); + + actions.next(new UpdateAdminSuccess({id: 'foo', changes: { ...admin }})); + + effects.updateAdminRoleReload$.subscribe(result => { + expect(result).toEqual(new LoadAdminRequest()); + }); + }); + }); + + describe('updateAdminRoleSuccess$', () => { + it('should fire a notification to the notification service', () => { + let payload = { id: 'foo', changes: { ...admin } }; + actions = new ReplaySubject(1); + + actions.next(new UpdateAdminSuccess(payload)); + + effects.updateAdminRoleSuccess$.subscribe(result => { + expect(result.type).toEqual(ADD_NOTIFICATION); + }); + }); + }); +}); diff --git a/ui/src/app/admin/effect/admin-collection.effect.ts b/ui/src/app/admin/effect/admin-collection.effect.ts index dece2bfb1..f867193a8 100644 --- a/ui/src/app/admin/effect/admin-collection.effect.ts +++ b/ui/src/app/admin/effect/admin-collection.effect.ts @@ -1,9 +1,6 @@ import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; -import { Store } from '@ngrx/store'; import { switchMap, map } from 'rxjs/operators'; - -import * as fromAdmin from '../reducer'; import { LoadAdminRequest, AdminCollectionActionTypes, @@ -91,7 +88,6 @@ export class AdminCollectionEffects { constructor( private actions$: Actions, - private adminService: AdminService, - private store: Store + private adminService: AdminService ) { } } diff --git a/ui/src/app/admin/effect/metadata-collection.effect.spec.ts b/ui/src/app/admin/effect/metadata-collection.effect.spec.ts new file mode 100644 index 000000000..d4d0bfcb4 --- /dev/null +++ b/ui/src/app/admin/effect/metadata-collection.effect.spec.ts @@ -0,0 +1,77 @@ +import { TestBed } from '@angular/core/testing'; +import { provideMockActions } from '@ngrx/effects/testing'; +import { Subject, Observable, of, ReplaySubject } from 'rxjs'; +import { StoreModule, combineReducers } from '@ngrx/store'; + +import { MetadataCollectionEffects } from './metadata-collection.effect'; +import { LoadMetadataRequest, LoadMetadataSuccess } from '../action/metadata-collection.action'; +import { ResolverService } from '../../metadata/domain/service/resolver.service'; +import { Metadata } from '../../metadata/domain/domain.type'; +import { MetadataResolver } from '../../metadata/domain/model'; +import * as fromI18n from '../../i18n/reducer'; +import { I18nService } from '../../i18n/service/i18n.service'; + +describe('Metadata Collection Effects', () => { + let effects: MetadataCollectionEffects; + let actions: Subject; + + let md: Metadata = { + serviceProviderName: 'foo', + name: 'foo', + type: 'bar', + resourceId: 'foo', + createdBy: 'admin' + }; + + let resolverService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({ + 'i18n': combineReducers(fromI18n.reducers), + }) + ], + providers: [ + { + provide: ResolverService, + useValue: jasmine.createSpyObj( + 'ResolverService', + [ + 'queryForAdmin', + 'update', + 'remove' + ] + ) + }, + { + provide: I18nService, + useValue: jasmine.createSpyObj( + 'I18nService', + [ + 'translate' + ] + ) + }, + MetadataCollectionEffects, + provideMockActions(() => actions), + ], + }); + + effects = TestBed.get(MetadataCollectionEffects); + resolverService = TestBed.get(ResolverService); + }); + + describe(`loadMetadatas$ effect`, () => { + it('should load admins and fire a success action', () => { + resolverService.queryForAdmin.and.returnValue(of([md])); + actions = new ReplaySubject(1); + + actions.next(new LoadMetadataRequest()); + + effects.loadMetadatas$.subscribe(result => { + expect(result).toEqual(new LoadMetadataSuccess([md] as MetadataResolver[])); + }); + }); + }); +}); diff --git a/ui/src/app/admin/effect/metadata-collection.effect.ts b/ui/src/app/admin/effect/metadata-collection.effect.ts index 17bf2e82d..2005a91bd 100644 --- a/ui/src/app/admin/effect/metadata-collection.effect.ts +++ b/ui/src/app/admin/effect/metadata-collection.effect.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; -import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { of } from 'rxjs'; import { map, catchError, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -127,7 +126,6 @@ export class MetadataCollectionEffects { constructor( private descriptorService: ResolverService, private actions$: Actions, - private router: Router, private store: Store, private i18nService: I18nService ) { } diff --git a/ui/src/app/admin/model/admin-entity.ts b/ui/src/app/admin/model/admin-entity.ts deleted file mode 100644 index 8993660b2..000000000 --- a/ui/src/app/admin/model/admin-entity.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Admin } from './admin'; - -export class AdminEntity implements Admin { - - username: string; - firstName: string; - lastName: string; - emailAddress: string; - - role: string; - - constructor( - properties: Admin - ) { - Object.assign(this, properties); - } -} diff --git a/ui/src/app/admin/service/admin.service.ts b/ui/src/app/admin/service/admin.service.ts index 452cc8f7a..7604c7dd5 100644 --- a/ui/src/app/admin/service/admin.service.ts +++ b/ui/src/app/admin/service/admin.service.ts @@ -3,7 +3,6 @@ import { Observable, of } from 'rxjs'; import { Admin } from '../model/admin'; import { HttpClient } from '@angular/common/http'; import { map, catchError } from 'rxjs/operators'; -import { AdminEntity } from '../model/admin-entity'; @Injectable() export class AdminService { @@ -17,16 +16,12 @@ export class AdminService { query(): Observable { return this.http.get( `${this.base}${this.endpoint}`, {} - ).pipe( - map(users => users.map(u => new AdminEntity(u))) ); } queryByRole(role: string): Observable { return this.http.get( `${this.base}${this.endpoint}/role/${role}`, {} - ).pipe( - map(users => users.map(u => new AdminEntity(u))) ); } diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 00123bf82..47130b4c7 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -18,7 +18,6 @@ import { CustomRouterStateSerializer } from './shared/util'; import { AuthorizedInterceptor } from './core/service/authorized.interceptor'; import { NotificationModule } from './notification/notification.module'; import { ErrorInterceptor } from './core/service/error.interceptor'; -import { NavigatorService } from './core/service/navigator.service'; import { ContentionModule } from './contention/contention.module'; import { SharedModule } from './shared/shared.module'; import { WizardModule } from './wizard/wizard.module'; @@ -64,8 +63,6 @@ import { NavigationService } from './core/service/navigation.service'; AppRoutingModule ], providers: [ - NavigatorService, - NavigationService, { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, { provide: HTTP_INTERCEPTORS, diff --git a/ui/src/app/core/core.module.spec.ts b/ui/src/app/core/core.module.spec.ts new file mode 100644 index 000000000..e19572a40 --- /dev/null +++ b/ui/src/app/core/core.module.spec.ts @@ -0,0 +1,23 @@ +import { TestBed } from '@angular/core/testing'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { CoreModule } from './core.module'; +import { UserService } from './service/user.service'; +import { HttpClientModule } from '@angular/common/http'; + +describe('Core Module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + EffectsModule.forRoot([]), + CoreModule.forRoot(), + HttpClientModule + ] + }); + }); + + it('should compile', () => { + expect(TestBed.get(UserService)).toBeDefined(); + }); +}); diff --git a/ui/src/app/core/core.module.ts b/ui/src/app/core/core.module.ts index b5606e630..fef2fd240 100644 --- a/ui/src/app/core/core.module.ts +++ b/ui/src/app/core/core.module.ts @@ -14,6 +14,8 @@ import { UserEffects } from './effect/user.effect'; import { HttpClientModule } from '@angular/common/http'; import { ModalService } from './service/modal.service'; import { DifferentialService } from './service/differential.service'; +import { NavigatorService } from './service/navigator.service'; +import { NavigationService } from './service/navigation.service'; export const COMPONENTS = []; @@ -36,7 +38,9 @@ export class CoreModule { FileService, ModalService, DifferentialService, - CanDeactivateGuard + CanDeactivateGuard, + NavigatorService, + NavigationService ] }; } diff --git a/ui/src/app/core/service/navigator.service.ts b/ui/src/app/core/service/navigator.service.ts index c54aa6931..c865c9a54 100644 --- a/ui/src/app/core/service/navigator.service.ts +++ b/ui/src/app/core/service/navigator.service.ts @@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'; export class NavigatorService { constructor() {} - get native(): any { + get native(): Navigator { return window.navigator; } -} /* istanbul ignore next */ +} diff --git a/ui/src/app/i18n/component/i18n-text.component.spec.ts b/ui/src/app/i18n/component/i18n-text.component.spec.ts new file mode 100644 index 000000000..ae84d3e17 --- /dev/null +++ b/ui/src/app/i18n/component/i18n-text.component.spec.ts @@ -0,0 +1,76 @@ +import { I18nService } from '../service/i18n.service'; +import { CommonModule } from '@angular/common'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; + +import * as fromI18n from '../reducer'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { Component } from '@angular/core'; +import { MessagesLoadSuccessAction } from '../action/message.action'; +import { I18nTextComponent } from './i18n-text.component'; +import { MockI18nService } from '../../../testing/i18n.stub'; + +@Component({ + template: ` + Word + ` +}) +class TestHostComponent { + private _foo: string; + + public get foo(): string { + return this._foo; + } + + public set foo(val: string) { + this._foo = val; + } +} + +describe('Component: I18n text translation', () => { + let fixture: ComponentFixture; + let instance: TestHostComponent; + let store: Store; + let service: I18nService; + + const msg = { + 'metadata-ui': 'User Interface / MDUI Information', + bar: 'bar', + baz: 'baz' + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: I18nService, useClass: MockI18nService } + ], + imports: [ + CommonModule, + StoreModule.forRoot({ + 'i18n': combineReducers(fromI18n.reducers), + }) + ], + declarations: [ + I18nTextComponent, + TestHostComponent + ], + }); + store = TestBed.get(Store); + service = TestBed.get(I18nService); + + spyOn(store, 'dispatch').and.callThrough(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + }); + + it('should set the correct text', () => { + instance.foo = 'metadata-ui'; + spyOn(service, 'translate').and.returnValue('foo'); + store.dispatch(new MessagesLoadSuccessAction(msg)); + + store.select(fromI18n.getMessages).subscribe(() => { + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toContain(msg['metadata-ui']); + }); + }); +}); diff --git a/ui/src/app/i18n/effect/message.effect.spec.ts b/ui/src/app/i18n/effect/message.effect.spec.ts index a0b628e6f..5f03b662c 100644 --- a/ui/src/app/i18n/effect/message.effect.spec.ts +++ b/ui/src/app/i18n/effect/message.effect.spec.ts @@ -10,7 +10,7 @@ import { I18nService } from '../service/i18n.service'; import { StoreModule, combineReducers, Store } from '@ngrx/store'; import * as fromI18n from '../reducer'; -xdescribe('I18n Message Effects', () => { +describe('I18n Message Effects', () => { let effects: MessageEffects; let actions: Subject; let i18nService: I18nService; @@ -20,7 +20,7 @@ xdescribe('I18n Message Effects', () => { TestBed.configureTestingModule({ imports: [ StoreModule.forRoot({ - core: combineReducers(fromI18n.reducers, { + i18n: combineReducers(fromI18n.reducers, { messages: { fetching: false, messages: null, @@ -32,7 +32,8 @@ xdescribe('I18n Message Effects', () => { ], providers: [ { - provide: I18nService, useValue: { + provide: I18nService, + useValue: { get: (locale: string) => of({}) } }, diff --git a/ui/src/app/i18n/i18n.module.spec.ts b/ui/src/app/i18n/i18n.module.spec.ts new file mode 100644 index 000000000..5306929cb --- /dev/null +++ b/ui/src/app/i18n/i18n.module.spec.ts @@ -0,0 +1,25 @@ +import { TestBed } from '@angular/core/testing'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; +import { I18nModule } from './i18n.module'; +import { I18nService } from './service/i18n.service'; +import { HttpClientModule } from '@angular/common/http'; +import { CoreModule } from '../core/core.module'; + +describe('I18n Module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + EffectsModule.forRoot([]), + I18nModule.forRoot(), + HttpClientModule, + CoreModule.forRoot() + ] + }); + }); + + it('should compile', () => { + expect(TestBed.get(I18nService)).toBeDefined(); + }); +}); diff --git a/ui/src/app/i18n/reducer/index.ts b/ui/src/app/i18n/reducer/index.ts index 68d5fc774..d8474cd18 100644 --- a/ui/src/app/i18n/reducer/index.ts +++ b/ui/src/app/i18n/reducer/index.ts @@ -5,7 +5,7 @@ import { import * as fromMessages from './message.reducer'; import * as fromRoot from '../../app.reducer'; -import { getCurrentLanguage, getCurrentCountry } from '../../shared/util'; +import { getCurrentLanguage } from '../../shared/util'; export interface I18nState { messages: fromMessages.MessageState; @@ -25,7 +25,6 @@ export const getMessageStateFn = (state: I18nState) => state.messages; export const getMessageState = createSelector(getCoreFeature, getMessageStateFn); export const getLocale = createSelector(getMessageState, fromMessages.getLocale); export const getLanguage = createSelector(getLocale, locale => getCurrentLanguage(locale)); -export const getCountry = createSelector(getLocale, locale => getCurrentCountry(locale)); export const getMessages = createSelector(getMessageState, fromMessages.getMessages); export const getFetchingMessages = createSelector(getMessageState, fromMessages.isFetching); diff --git a/ui/src/app/i18n/service/i18n.service.spec.ts b/ui/src/app/i18n/service/i18n.service.spec.ts index 9a99a06e8..dd4220bf5 100644 --- a/ui/src/app/i18n/service/i18n.service.spec.ts +++ b/ui/src/app/i18n/service/i18n.service.spec.ts @@ -1,8 +1,8 @@ import { TestBed, async, inject } from '@angular/core/testing'; import { I18nService } from './i18n.service'; -import { HttpClientModule } from '@angular/common/http'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { HttpClientModule, HttpRequest } from '@angular/common/http'; import { NavigatorService } from '../../core/service/navigator.service'; +import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; describe('i18n Service', () => { let service: I18nService; @@ -15,7 +15,15 @@ describe('i18n Service', () => { HttpClientTestingModule ], providers: [ - NavigatorService, + { + provide: NavigatorService, + useValue: { + native: { + language: 'en-US', + languages: ['en-US', 'zh-CN', 'ja-JP'] + } + } + }, I18nService ] }); @@ -27,15 +35,24 @@ describe('i18n Service', () => { expect(service).toBeDefined(); }); - describe('getCurrentLanguage method', () => { - it('should return the current language', () => { - expect(service.getCurrentLanguage()).toEqual('en'); - }); + describe('get method', () => { + it(`should send an expected GET request`, async(inject([I18nService, HttpTestingController], + (i18n: I18nService, backend: HttpTestingController) => { + const lang = 'en'; + i18n.get(lang).subscribe(); + + backend.expectOne((req: HttpRequest) => { + return req.url === `${service.base}${service.path}` + && req.method === 'GET' + && req.params.get('lang') === lang; + }, `GET attributes by term`); + } + ))); }); - xdescribe('getCurrentCountry method', () => { + describe('getCurrentLanguage method', () => { it('should return the current language', () => { - expect(service.getCurrentCountry()).toEqual('US'); + expect(service.getCurrentLanguage()).toEqual('en'); }); }); @@ -47,7 +64,18 @@ describe('i18n Service', () => { describe('translate method', () => { it('should translate the provided message', () => { - expect(service.translate('foo', { baz: 'baz' }, { foo: 'bar { baz }' })); + expect(service.translate('foo', { baz: 'baz' }, { foo: 'bar { baz }' })).toEqual('bar baz'); + }); + + it('should accept provided messages to check', () => { + expect(service.translate('foo', null, { baz: 'bar { baz }' })).toEqual('foo'); + expect(service.translate('foo', null, {})).toEqual(''); + }); + }); + + describe('interpolate method', () => { + it('should return provided value if no interpolation strings are passed', () => { + expect(service.interpolate('foo')).toEqual('foo'); }); }); }); diff --git a/ui/src/app/i18n/service/i18n.service.ts b/ui/src/app/i18n/service/i18n.service.ts index a50bae832..4d405c5b2 100644 --- a/ui/src/app/i18n/service/i18n.service.ts +++ b/ui/src/app/i18n/service/i18n.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { NavigatorService } from '../../core/service/navigator.service'; -import { getCurrentLanguage, getCurrentCountry, getCurrentLocale } from '../../shared/util'; +import { getCurrentLanguage, getCurrentLocale } from '../../shared/util'; import { Messages } from '../model/Messages'; @Injectable() @@ -28,10 +28,6 @@ export class I18nService { return getCurrentLanguage(this.navigator.native); } - getCurrentCountry(): string { - return getCurrentCountry(this.navigator.native); - } - getCurrentLocale(): string { return getCurrentLocale(this.navigator.native); } diff --git a/ui/src/app/metadata/configuration/reducer/utilities.ts b/ui/src/app/metadata/configuration/reducer/utilities.ts index d73039306..1cba47fbd 100644 --- a/ui/src/app/metadata/configuration/reducer/utilities.ts +++ b/ui/src/app/metadata/configuration/reducer/utilities.ts @@ -135,4 +135,4 @@ export const getLimitedPropertiesFn = (properties: SectionProperty[]) => { return parsed; }) ]); -}; \ No newline at end of file +}; diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts index f9d614c2e..0e4c84e6c 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.spec.ts @@ -44,5 +44,53 @@ describe('Metadata Source Base class', () => { '/relyingPartyOverrides' ]); }); + + describe('root validator', () => { + it('should check for child errors', () => { + const validators = getValidators([]); + const validator = validators['/']; + const getPropertySpy = jasmine.createSpy('getProperty'); + const relyingPartyOverrides = { foo: 'bar', baz: 'foo' }; + const value = { relyingPartyOverrides }; + const error = { + code: 'INVALID_SIGNING', + path: `#/relyingPartyOverrides`, + message: 'message.invalid-signing', + params: [relyingPartyOverrides] + }; + spyOn(validators, '/relyingPartyOverrides').and.returnValue(error); + + const validated = validator(value, null, { getProperty: getPropertySpy }); + + expect(validated).toEqual([error]); + }); + }); + + describe('relying party validator', () => { + it('should check for child errors', () => { + const validators = getValidators([]); + const validator = validators['/relyingPartyOverrides']; + const relyingPartyOverrides = { signAssertion: false, dontSignResponse: true }; + const error = { + code: 'INVALID_SIGNING', + path: `#/relyingPartyOverrides`, + message: 'message.invalid-signing', + params: [relyingPartyOverrides] + }; + + const validated = validator(relyingPartyOverrides, {path: '/relyingPartyOverrides'}); + + expect(validated).toEqual(error); + }); + + it('should return null if no error detected', () => { + const validators = getValidators([]); + const validator = validators['/relyingPartyOverrides']; + + expect(validator({ signAssertion: true, dontSignResponse: true }, { path: '/relyingPartyOverrides' })).toEqual(null); + expect(validator({ signAssertion: true, dontSignResponse: false }, { path: '/relyingPartyOverrides' })).toEqual(null); + expect(validator({ signAssertion: false, dontSignResponse: false }, { path: '/relyingPartyOverrides' })).toEqual(null); + }); + }); }); }); diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index 0e8a0c209..360979782 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -90,17 +90,15 @@ export class MetadataSourceBase implements Wizard { return err; }, '/relyingPartyOverrides': (value, property, form) => { - let errors; if (!value.signAssertion && value.dontSignResponse) { - errors = []; - errors.push({ + return { code: 'INVALID_SIGNING', path: `#${property.path}`, message: 'message.invalid-signing', params: [value] - }); + }; } - return errors; + return null; } }; return validators; diff --git a/ui/src/app/metadata/manager/component/delete-dialog.component.spec.ts b/ui/src/app/metadata/manager/component/delete-dialog.component.spec.ts new file mode 100644 index 000000000..ec8fdc665 --- /dev/null +++ b/ui/src/app/metadata/manager/component/delete-dialog.component.spec.ts @@ -0,0 +1,55 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbDropdownModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { DeleteDialogComponent } from './delete-dialog.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(DeleteDialogComponent, { static: true }) + public componentUnderTest: DeleteDialogComponent; +} + +describe('Delete Dialog (modal) Component', () => { + + let app: DeleteDialogComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + RouterTestingModule + ], + declarations: [ + DeleteDialogComponent, + TestHostComponent + ], + providers: [ + { + provide: NgbActiveModal, + useValue: jasmine.createSpyObj('activeModal', [ + 'close', + 'dismiss' + ]) + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/metadata/provider/component/delete-filter.component.spec.ts b/ui/src/app/metadata/provider/component/delete-filter.component.spec.ts new file mode 100644 index 000000000..08b4007cc --- /dev/null +++ b/ui/src/app/metadata/provider/component/delete-filter.component.spec.ts @@ -0,0 +1,55 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { NgbDropdownModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { DeleteFilterComponent } from './delete-filter.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(DeleteFilterComponent, { static: true }) + public componentUnderTest: DeleteFilterComponent; +} + +describe('Delete Filter (modal) Component', () => { + + let app: DeleteFilterComponent; + let fixture: ComponentFixture; + let instance: TestHostComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + RouterTestingModule + ], + declarations: [ + DeleteFilterComponent, + TestHostComponent + ], + providers: [ + { + provide: NgbActiveModal, + useValue: jasmine.createSpyObj('activeModal', [ + 'close', + 'dismiss' + ]) + } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile without error', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts index 2f76d7d5d..7c0099484 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.spec.ts @@ -12,6 +12,8 @@ import { SummaryPropertyComponent } from '../../domain/component/summary-propert import * as fromWizard from '../../../wizard/reducer'; import { MockI18nModule } from '../../../../testing/i18n.stub'; import { MetadataConfigurationComponentStub } from '../../../../testing/metadata-configuration.stub'; +import { WizardComponent } from '../../../wizard/component/wizard.component'; +import { EffectsModule } from '@ngrx/effects'; @Component({ template: ` @@ -33,6 +35,7 @@ describe('Provider Wizard Component', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ + WizardModule.forRoot(), WizardModule, NgbDropdownModule, NgbPopoverModule, @@ -41,6 +44,7 @@ describe('Provider Wizard Component', () => { provider: combineReducers(fromRoot.reducers), wizard: combineReducers(fromWizard.reducers) }), + EffectsModule.forRoot([]), MockI18nModule ], declarations: [ diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts index 68e19359b..4176905de 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts @@ -117,5 +117,14 @@ describe('FileBackedHttpMetadataProviderWizard', () => { '/metadataURL' ]); }); + + it('should validate the metadataUrl format', () => { + const validators = getValidators(); + const path = '/metadataURL'; + const validator = validators[path]; + + expect(validator('foo', { path })).toBeDefined(); + expect(validator('http://foo.com', { path })).toBeNull(); + }); }); }); diff --git a/ui/src/app/metadata/resolver/component/finish-form.component.html b/ui/src/app/metadata/resolver/component/finish-form.component.html index 881c68206..8a3388f3f 100644 --- a/ui/src/app/metadata/resolver/component/finish-form.component.html +++ b/ui/src/app/metadata/resolver/component/finish-form.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/ui/src/app/metadata/resolver/component/finish-form.component.spec.ts b/ui/src/app/metadata/resolver/component/finish-form.component.spec.ts index 4bf37267c..31cd48302 100644 --- a/ui/src/app/metadata/resolver/component/finish-form.component.spec.ts +++ b/ui/src/app/metadata/resolver/component/finish-form.component.spec.ts @@ -83,7 +83,7 @@ describe('Finished Form Component', () => { expect(form.form.reset).toHaveBeenCalled(); }); - xit('should reset the form with serviceEnabled = false if no resolver', () => { + it('should reset the form with serviceEnabled = false if no resolver', () => { spyOn(form.form, 'reset').and.callThrough(); delete instance.resolver; fixture.detectChanges(); diff --git a/ui/src/app/shared/component/info-icon.component.spec.ts b/ui/src/app/shared/component/info-icon.component.spec.ts new file mode 100644 index 000000000..17fc4d216 --- /dev/null +++ b/ui/src/app/shared/component/info-icon.component.spec.ts @@ -0,0 +1,49 @@ +import { Component, ViewChild, Renderer, RootRenderer } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockI18nModule } from '../../../testing/i18n.stub'; +import { InfoIconComponent } from './info-icon.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(InfoIconComponent, { static: true }) + public componentUnderTest: InfoIconComponent; + + public description = 'Foo bar baz'; +} + +describe('Info Icon Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: InfoIconComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbPopoverModule, + MockI18nModule + ], + declarations: [ + TestHostComponent, + InfoIconComponent + ], + providers: [ + Renderer + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should display an information icon', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/shared/component/info-icon.component.ts b/ui/src/app/shared/component/info-icon.component.ts index 84ac09d0a..e11e928ad 100644 --- a/ui/src/app/shared/component/info-icon.component.ts +++ b/ui/src/app/shared/component/info-icon.component.ts @@ -21,7 +21,6 @@ export class InfoIconComponent { private renderer: Renderer ) { } focus(element): void { - console.log(element.elementRef.nativeElement); this.renderer.invokeElementMethod(element.elementRef.nativeElement, 'focus'); } } diff --git a/ui/src/app/shared/component/valid-form-icon.component.spec.ts b/ui/src/app/shared/component/valid-form-icon.component.spec.ts new file mode 100644 index 000000000..c15c39e4d --- /dev/null +++ b/ui/src/app/shared/component/valid-form-icon.component.spec.ts @@ -0,0 +1,49 @@ +import { Component, ViewChild, Renderer, RootRenderer } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; +import { MockI18nModule } from '../../../testing/i18n.stub'; +import { ValidFormIconComponent } from './valid-form-icon.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(ValidFormIconComponent, { static: true }) + public componentUnderTest: ValidFormIconComponent; + + public status = 'INVALID'; +} + +describe('Info Icon Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: ValidFormIconComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbPopoverModule, + MockI18nModule + ], + declarations: [ + TestHostComponent, + ValidFormIconComponent + ], + providers: [ + Renderer + ] + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should display an information icon', async(() => { + expect(app).toBeTruthy(); + })); +}); diff --git a/ui/src/app/shared/pipe/replace.pipe.ts b/ui/src/app/shared/pipe/replace.pipe.ts deleted file mode 100644 index 16fd97ce3..000000000 --- a/ui/src/app/shared/pipe/replace.pipe.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PipeTransform, Pipe } from '@angular/core'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; - -@Pipe({ name: 'replace' }) -export class ReplacePipe implements PipeTransform { - constructor(public sanitizer: DomSanitizer) { } - transform(value: string, query: { [propName: string]: string }): SafeHtml { - return ''; - } -} diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index e033083a0..75dd7911c 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -9,7 +9,6 @@ import { ValidFormIconComponent } from './component/valid-form-icon.component'; import { PrettyXml } from './pipe/pretty-xml.pipe'; import { ToggleSwitchComponent } from './switch/switch.component'; import { ContenteditableDirective } from './contenteditable/contenteditable.directive'; -import { ReplacePipe } from './pipe/replace.pipe'; import { I18nModule } from '../i18n/i18n.module'; import { CustomDatePipe } from './pipe/date.pipe'; import { InfoIconComponent } from './component/info-icon.component'; @@ -31,7 +30,6 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; InputDefaultsDirective, ValidFormIconComponent, PrettyXml, - ReplacePipe, CustomDatePipe, ContenteditableDirective, InfoIconComponent @@ -47,7 +45,6 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; ValidFormIconComponent, ValidationClassDirective, ContenteditableDirective, - ReplacePipe, CustomDatePipe, InfoIconComponent ] diff --git a/ui/src/app/wizard/component/wizard.component.spec.ts b/ui/src/app/wizard/component/wizard.component.spec.ts index 250db89da..9105e2d82 100644 --- a/ui/src/app/wizard/component/wizard.component.spec.ts +++ b/ui/src/app/wizard/component/wizard.component.spec.ts @@ -5,7 +5,8 @@ import { StoreModule, Store, combineReducers } from '@ngrx/store'; import * as fromWizard from '../reducer'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { WizardComponent, ICONS } from './wizard.component'; +import { WizardComponent } from './wizard.component'; +import { WizardService } from '../service/wizard.service'; import { MockI18nModule } from '../../../testing/i18n.stub'; @Component({ @@ -39,6 +40,9 @@ describe('Wizard Component', () => { WizardComponent, TestHostComponent ], + providers: [ + WizardService + ] }).compileComponents(); store = TestBed.get(Store); @@ -53,14 +57,4 @@ describe('Wizard Component', () => { it('should compile without error', () => { expect(app).toBeTruthy(); }); - - describe('getIcon method', () => { - it('should return the check string for the last index', () => { - expect(app.getIcon({ index: 'foo' }, { index: 'foo' })).toEqual(ICONS.CHECK); - }); - it('should return the index icon for other indexes', () => { - expect(app.getIcon({ index: 'foo' }, { index: 'bar' })).toEqual(ICONS.INDEX); - expect(app.getIcon({ index: 'foo' }, null)).toEqual(ICONS.INDEX); - }); - }); }); diff --git a/ui/src/app/wizard/component/wizard.component.ts b/ui/src/app/wizard/component/wizard.component.ts index 7b4aa8ff4..1b7103c93 100644 --- a/ui/src/app/wizard/component/wizard.component.ts +++ b/ui/src/app/wizard/component/wizard.component.ts @@ -4,12 +4,7 @@ import { Wizard, WizardStep } from '../model'; import * as fromWizard from '../reducer'; import { Observable } from 'rxjs'; import { withLatestFrom, map } from 'rxjs/operators'; - -export enum ICONS { - CHECK = 'CHECK', - INDEX = 'INDEX' -} - +import { WizardService } from '../service/wizard.service'; /*tslint:disable:component-selector */ @Component({ @@ -38,10 +33,11 @@ export class WizardComponent { currentIcon$: Observable; - icons = ICONS; + icons = this.service.icons; constructor( - private store: Store + private store: Store, + private service: WizardService ) { this.index$ = this.store.select(fromWizard.getWizardIndex); this.definition$ = this.store.select(fromWizard.getWizardDefinition); @@ -53,11 +49,7 @@ export class WizardComponent { this.currentIcon$ = this.current$.pipe( withLatestFrom(this.last$), - map(([current, last]) => this.getIcon(current, last)) + map(([current, last]) => this.service.getIcon(current, last)) ); } - - getIcon(current, last): string { - return (last && current.index === last.index) ? ICONS.CHECK : ICONS.INDEX; - } } diff --git a/ui/src/app/wizard/service/wizard.service.spec.ts b/ui/src/app/wizard/service/wizard.service.spec.ts new file mode 100644 index 000000000..f7aa204cd --- /dev/null +++ b/ui/src/app/wizard/service/wizard.service.spec.ts @@ -0,0 +1,26 @@ +import { WizardService, ICONS } from './wizard.service'; +import { TestBed } from '@angular/core/testing'; + +describe('Wizard Service', () => { + let service: WizardService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + WizardService + ] + }); + + service = TestBed.get(WizardService); + }); + + describe('getIcon method', () => { + it('should return the check string for the last index', () => { + expect(service.getIcon({ index: 'foo' }, { index: 'foo' })).toEqual(ICONS.CHECK); + }); + it('should return the index icon for other indexes', () => { + expect(service.getIcon({ index: 'foo' }, { index: 'bar' })).toEqual(ICONS.INDEX); + expect(service.getIcon({ index: 'foo' }, null)).toEqual(ICONS.INDEX); + }); + }); +}); diff --git a/ui/src/app/wizard/service/wizard.service.ts b/ui/src/app/wizard/service/wizard.service.ts new file mode 100644 index 000000000..83eebe089 --- /dev/null +++ b/ui/src/app/wizard/service/wizard.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; + + +export enum ICONS { + CHECK = 'CHECK', + INDEX = 'INDEX' +} + +@Injectable() +export class WizardService { + + public icons = ICONS; + + constructor() { } + + getIcon(current, last): string { + return (last && current.index === last.index) ? ICONS.CHECK : ICONS.INDEX; + } +} diff --git a/ui/src/app/wizard/wizard.module.spec.ts b/ui/src/app/wizard/wizard.module.spec.ts new file mode 100644 index 000000000..547090d8f --- /dev/null +++ b/ui/src/app/wizard/wizard.module.spec.ts @@ -0,0 +1,21 @@ +import { WizardModule } from './wizard.module'; +import { TestBed } from '@angular/core/testing'; +import { WizardService } from './service/wizard.service'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +describe('Wizard Module', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + StoreModule.forRoot({}), + EffectsModule.forRoot([]), + WizardModule.forRoot() + ] + }); + }); + + it('should compile', () => { + expect(TestBed.get(WizardService)).toBeDefined(); + }); +}); diff --git a/ui/src/app/wizard/wizard.module.ts b/ui/src/app/wizard/wizard.module.ts index a02decbb8..34a196d14 100644 --- a/ui/src/app/wizard/wizard.module.ts +++ b/ui/src/app/wizard/wizard.module.ts @@ -6,6 +6,7 @@ import { EffectsModule } from '@ngrx/effects'; import { WizardComponent } from './component/wizard.component'; import { reducers } from './reducer'; import { I18nModule } from '../i18n/i18n.module'; +import { WizardService } from './service/wizard.service'; @NgModule({ declarations: [ @@ -24,7 +25,7 @@ export class WizardModule { static forRoot(): ModuleWithProviders { return { ngModule: RootWizardModule, - providers: [] + providers: [WizardService] }; } } diff --git a/ui/src/testing/descriptor.service.stub.ts b/ui/src/testing/descriptor.service.stub.ts new file mode 100644 index 000000000..e69de29bb