diff --git a/client/src/app/metamodel/effects/attribute.effects.ts b/client/src/app/metamodel/effects/attribute.effects.ts index 855fe3301ff2a861979aeea5d3b851e64673e473..59e6c66114e1ce62e7b1bac1aa3b0f24452b7d08 100644 --- a/client/src/app/metamodel/effects/attribute.effects.ts +++ b/client/src/app/metamodel/effects/attribute.effects.ts @@ -18,7 +18,7 @@ import { ToastrService } from 'ngx-toastr'; import * as attributeActions from '../actions/attribute.actions'; import { AttributeService } from '../services/attribute.service'; import * as datasetSelector from '../selectors/dataset.selector'; - + @Injectable() export class AttributeEffects { loadAttributes$ = createEffect(() => @@ -33,8 +33,8 @@ export class AttributeEffects { ) ) ); - - addAttribute$ = createEffect(() => + + addAttribute$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.addAttribute), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -47,23 +47,23 @@ export class AttributeEffects { ) ); - addAttributeSuccess$ = createEffect(() => + addAttributeSuccess$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.addAttributeSuccess), tap(() => { this.toastr.success('Attribute successfully added', 'The new attribute was added into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - addAttributeFail$ = createEffect(() => + addAttributeFail$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.addAttributeFail), tap(() => this.toastr.error('Failure to add attribute', 'The new attribute could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editAttribute$ = createEffect(() => + editAttribute$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.editAttribute), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -76,14 +76,14 @@ export class AttributeEffects { ) ); - editAttributeFail$ = createEffect(() => + editAttributeFail$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.editAttributeFail), tap(() => this.toastr.error('Failure to edit attribute', 'The existing attribute could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteAttribute$ = createEffect(() => + deleteAttribute$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.deleteAttribute), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -96,20 +96,20 @@ export class AttributeEffects { ) ); - deleteAttributeSuccess$ = createEffect(() => + deleteAttributeSuccess$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.deleteAttributeSuccess), tap(() => this.toastr.success('Attribute successfully deleted', 'The existing attribute has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteAttributeFail$ = createEffect(() => + deleteAttributeFail$ = createEffect(() => this.actions$.pipe( ofType(attributeActions.deleteAttributeFail), tap(() => this.toastr.error('Failure to delete attribute', 'The existing attribute could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private attributeService: AttributeService, diff --git a/client/src/app/metamodel/effects/column.effects.spec.ts b/client/src/app/metamodel/effects/column.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef28d818d0c4adcd08c1e4236eadeeef5ce242d4 --- /dev/null +++ b/client/src/app/metamodel/effects/column.effects.spec.ts @@ -0,0 +1,88 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { ColumnEffects } from './column.effects'; +import { ColumnService } from '../services/column.service'; +import * as columnActions from '../actions/column.actions'; +import { COLUMN_LIST } from '../../../test-data'; +import * as datasetSelector from '../selectors/dataset.selector'; + +describe('[Metamodel][Effects] ColumnEffects', () => { + let actions = new Observable(); + let effects: ColumnEffects; + let metadata: EffectsMetadata<ColumnEffects>; + let service: ColumnService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { metamodel: { } }; + let mockDatasetSelectorSelectDatasetNameByRoute; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + ColumnEffects, + { provide: ColumnService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions), + provideMockStore({ initialState }) + ] + }).compileComponents(); + effects = TestBed.inject(ColumnEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(ColumnService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + store = TestBed.inject(MockStore); + mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + datasetSelector.selectDatasetNameByRoute,'' + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadOutputFamilies$ effect', () => { + it('should dispatch the loadColumnListSuccess action on success', () => { + mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + datasetSelector.selectDatasetNameByRoute, 'myDataset' + ); + + const action = columnActions.loadColumnList(); + const outcome = columnActions.loadColumnListSuccess({ columns: COLUMN_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: COLUMN_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveColumns = jest.fn(() => response); + + expect(effects.loadColumns$).toBeObservable(expected); + expect(service.retrieveColumns).toHaveBeenCalledWith('myDataset'); + }); + + it('should dispatch the loadColumnListFail action on HTTP failure', () => { + const action = columnActions.loadColumnList(); + const error = new Error(); + const outcome = columnActions.loadColumnListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveColumns = jest.fn(() => response); + + expect(effects.loadColumns$).toBeObservable(expected); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/column.effects.ts b/client/src/app/metamodel/effects/column.effects.ts index 53399092ae075c8302d67a01ed281cd5d35b6fd3..0e11b28f27add041602baf97520a2e0c021eb6fc 100644 --- a/client/src/app/metamodel/effects/column.effects.ts +++ b/client/src/app/metamodel/effects/column.effects.ts @@ -17,10 +17,18 @@ import { map, mergeMap, catchError } from 'rxjs/operators'; import * as columnActions from '../actions/column.actions'; import { ColumnService } from '../services/column.service'; import * as datasetSelector from '../selectors/dataset.selector'; - + +/** + * @class + * @classdesc Column effects. + */ @Injectable() export class ColumnEffects { - loadColumns$ = createEffect(() => + + /** + * Calls action to retrieve survey list. + */ + loadColumns$ = createEffect((): any => this.actions$.pipe( ofType(columnActions.loadColumnList), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -32,7 +40,7 @@ export class ColumnEffects { ) ) ); - + constructor( private actions$: Actions, private columnService: ColumnService, diff --git a/client/src/app/metamodel/effects/criteria-family.effects.spec.ts b/client/src/app/metamodel/effects/criteria-family.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..10ac482fdfe156d74aab60854e244b8b47628691 --- /dev/null +++ b/client/src/app/metamodel/effects/criteria-family.effects.spec.ts @@ -0,0 +1,312 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; + +import { ToastrService } from 'ngx-toastr'; +import { CriteriaFamilyEffects } from './criteria-family.effects'; +import { CriteriaFamilyService } from '../services/criteria-family.service'; +import * as criteriaFamilyActions from '../actions/criteria-family.actions'; +import { CRITERIA_FAMILY, CRITERIA_FAMILY_LIST } from '../../../test-data'; +import * as datasetSelector from '../selectors/dataset.selector'; + +describe('[Metamodel][Effects] CriteriaFamilyEffects', () => { + let actions = new Observable(); + let effects: CriteriaFamilyEffects; + let metadata: EffectsMetadata<CriteriaFamilyEffects>; + let service: CriteriaFamilyService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { metamodel: { } }; + let mockDatasetSelectorSelectDatasetNameByRoute; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + CriteriaFamilyEffects, + { provide: CriteriaFamilyService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions), + provideMockStore({ initialState }) + ] + }).compileComponents(); + effects = TestBed.inject(CriteriaFamilyEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(CriteriaFamilyService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + store = TestBed.inject(MockStore); + mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + datasetSelector.selectDatasetNameByRoute,'' + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadCriteriaFamilies$ effect', () => { + it('should dispatch the loadCriteriaFamilyListSuccess action on success', () => { + mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + datasetSelector.selectDatasetNameByRoute, 'myDataset' + ); + + const action = criteriaFamilyActions.loadCriteriaFamilyList(); + const outcome = criteriaFamilyActions.loadCriteriaFamilyListSuccess({ criteriaFamilies: CRITERIA_FAMILY_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: CRITERIA_FAMILY_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveCriteriaFamilyList = jest.fn(() => response); + + expect(effects.loadCriteriaFamilies$).toBeObservable(expected); + expect(service.retrieveCriteriaFamilyList).toHaveBeenCalledWith('myDataset'); + }); + + it('should dispatch the loadCriteriaFamilyListFail action on HTTP failure', () => { + const action = criteriaFamilyActions.loadCriteriaFamilyList(); + const error = new Error(); + const outcome = criteriaFamilyActions.loadCriteriaFamilyListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveCriteriaFamilyList = jest.fn(() => response); + + expect(effects.loadCriteriaFamilies$).toBeObservable(expected); + }); + }); + + describe('addCriteriaFamilies$ effect', () => { + it('should dispatch the addCriteriaFamilySuccess action on success', () => { + mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + datasetSelector.selectDatasetNameByRoute, 'myDataset' + ); + + const action = criteriaFamilyActions.addCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const outcome = criteriaFamilyActions.addCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: CRITERIA_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.addCriteriaFamily = jest.fn(() => response); + + expect(effects.addCriteriaFamily$).toBeObservable(expected); + expect(service.addCriteriaFamily).toHaveBeenCalledWith('myDataset', CRITERIA_FAMILY); + }); + + it('should dispatch the addCriteriaFamilyFail action on HTTP failure', () => { + const action = criteriaFamilyActions.addCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const error = new Error(); + const outcome = criteriaFamilyActions.addCriteriaFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.addCriteriaFamily = jest.fn(() => response); + + expect(effects.addCriteriaFamily$).toBeObservable(expected); + }); + }); + + describe('addCriteriaFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addCriteriaFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const spy = jest.spyOn(toastr, 'success'); + const action = criteriaFamilyActions.addCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addCriteriaFamilySuccess$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Criteria family successfully added', + 'The new criteria family was added into the database' + ); + }); + }); + + describe('addCriteriaFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addCriteriaFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = criteriaFamilyActions.addCriteriaFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addCriteriaFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to add criteria family', + 'The new criteria family could not be added into the database' + ); + }); + }); + + describe('editCriteriaFamily$ effect', () => { + it('should dispatch the editCriteriaFamilySuccess action on success', () => { + const action = criteriaFamilyActions.editCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const outcome = criteriaFamilyActions.editCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: CRITERIA_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.editCriteriaFamily = jest.fn(() => response); + + expect(effects.editCriteriaFamily$).toBeObservable(expected); + }); + + it('should dispatch the editCriteriaFamilyFail action on HTTP failure', () => { + const action = criteriaFamilyActions.editCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const error = new Error(); + const outcome = criteriaFamilyActions.editCriteriaFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.editCriteriaFamily = jest.fn(() => response); + + expect(effects.editCriteriaFamily$).toBeObservable(expected); + }); + }); + + describe('editCriteriaFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editCriteriaFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const spy = jest.spyOn(toastr, 'success'); + const action = criteriaFamilyActions.editCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editCriteriaFamilySuccess$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Criteria family successfully edited', + 'The existing criteria family has been edited into the database' + ); + }); + }); + + describe('editCriteriaFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editCriteriaFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = criteriaFamilyActions.editCriteriaFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editCriteriaFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to edit criteria family', + 'The existing criteria family could not be edited into the database' + ); + }); + }); + + describe('deleteCriteriaFamily$ effect', () => { + it('should dispatch the deleteSurveySuccess action on success', () => { + const action = criteriaFamilyActions.deleteCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const outcome = criteriaFamilyActions.deleteCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: CRITERIA_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.deleteCriteriaFamily = jest.fn(() => response); + + expect(effects.deleteCriteriaFamily$).toBeObservable(expected); + }); + + it('should dispatch the deleteCriteriaFamilyFail action on HTTP failure', () => { + const action = criteriaFamilyActions.deleteCriteriaFamily({ criteriaFamily: CRITERIA_FAMILY }); + const error = new Error(); + const outcome = criteriaFamilyActions.deleteCriteriaFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.deleteCriteriaFamily = jest.fn(() => response); + + expect(effects.deleteCriteriaFamily$).toBeObservable(expected); + }); + }); + + describe('deleteCriteriaFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteCriteriaFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const spy = jest.spyOn(toastr, 'success'); + const action = criteriaFamilyActions.deleteCriteriaFamilySuccess({ criteriaFamily: CRITERIA_FAMILY }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteCriteriaFamilySuccess$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Criteria family successfully deleted', + 'The existing criteria family has been deleted' + ); + }); + }); + + describe('deleteCriteriaFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteCriteriaFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = criteriaFamilyActions.deleteCriteriaFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteCriteriaFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to delete criteria family', + 'The existing criteria family could not be deleted from the database' + ); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/criteria-family.effects.ts b/client/src/app/metamodel/effects/criteria-family.effects.ts index 95894f4cf924558bf1f2fb125c6eebece5d65237..e72d3178a3aa9d1ddac4032d69cfa1a741d96035 100644 --- a/client/src/app/metamodel/effects/criteria-family.effects.ts +++ b/client/src/app/metamodel/effects/criteria-family.effects.ts @@ -18,10 +18,18 @@ import { ToastrService } from 'ngx-toastr'; import * as criteriaFamilyActions from '../actions/criteria-family.actions'; import { CriteriaFamilyService } from '../services/criteria-family.service'; import * as datasetSelector from '../selectors/dataset.selector'; - + +/** + * @class + * @classdesc Criteria family effects. + */ @Injectable() export class CriteriaFamilyEffects { - loadCriteriaFamilys$ = createEffect(() => + + /** + * Calls action to retrieve criteria family list. + */ + loadCriteriaFamilies$ = createEffect((): any => this.actions$.pipe( ofType(criteriaFamilyActions.loadCriteriaFamilyList), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -34,7 +42,10 @@ export class CriteriaFamilyEffects { ) ); - addCriteriaFamily$ = createEffect(() => + /** + * Calls action to add a criteria family. + */ + addCriteriaFamily$ = createEffect((): any => this.actions$.pipe( ofType(criteriaFamilyActions.addCriteriaFamily), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -47,21 +58,30 @@ export class CriteriaFamilyEffects { ) ); - addCriteriaFamilySuccess$ = createEffect(() => + /** + * Displays add criteria family success notification. + */ + addCriteriaFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.addCriteriaFamilySuccess), tap(() => this.toastr.success('Criteria family successfully added', 'The new criteria family was added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - addCriteriaFamilyFail$ = createEffect(() => + /** + * Displays add criteria family error notification. + */ + addCriteriaFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.addCriteriaFamilyFail), tap(() => this.toastr.error('Failure to add criteria family', 'The new criteria family could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editCriteriaFamily$ = createEffect(() => + /** + * Calls action to modify a criteria family. + */ + editCriteriaFamily$ = createEffect((): any => this.actions$.pipe( ofType(criteriaFamilyActions.editCriteriaFamily), mergeMap(action => this.criteriaFamilyService.editCriteriaFamily(action.criteriaFamily) @@ -73,21 +93,30 @@ export class CriteriaFamilyEffects { ) ); - editCriteriaFamilySuccess$ = createEffect(() => + /** + * Displays edit criteria family success notification. + */ + editCriteriaFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.editCriteriaFamilySuccess), tap(() => this.toastr.success('Criteria family successfully edited', 'The existing criteria family has been edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editCriteriaFamilyFail$ = createEffect(() => + /** + * Displays edit criteria family error notification. + */ + editCriteriaFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.editCriteriaFamilyFail), tap(() => this.toastr.error('Failure to edit criteria family', 'The existing criteria family could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteCriteriaFamily$ = createEffect(() => + /** + * Calls action to remove a criteria family. + */ + deleteCriteriaFamily$ = createEffect((): any => this.actions$.pipe( ofType(criteriaFamilyActions.deleteCriteriaFamily), mergeMap(action => this.criteriaFamilyService.deleteCriteriaFamily(action.criteriaFamily.id) @@ -99,20 +128,26 @@ export class CriteriaFamilyEffects { ) ); - deleteCriteriaFamilySuccess$ = createEffect(() => + /** + * Displays delete criteria family success notification. + */ + deleteCriteriaFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.deleteCriteriaFamilySuccess), tap(() => this.toastr.success('Criteria family successfully deleted', 'The existing criteria family has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteCriteriaFamilyFail$ = createEffect(() => + /** + * Displays delete criteria family error notification. + */ + deleteCriteriaFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(criteriaFamilyActions.deleteCriteriaFamilyFail), tap(() => this.toastr.error('Failure to delete criteria family', 'The existing criteria family could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private criteriaFamilyService: CriteriaFamilyService, diff --git a/client/src/app/metamodel/effects/database.effects.spec.ts b/client/src/app/metamodel/effects/database.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..65326e24aa2d975960b63f61662f57a8254f0c90 --- /dev/null +++ b/client/src/app/metamodel/effects/database.effects.spec.ts @@ -0,0 +1,298 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { DatabaseEffects } from './database.effects'; +import { DatabaseService } from '../services/database.service'; +import * as databaseActions from '../actions/database.actions'; +import { DATABASE, DATABASE_LIST } from '../../../test-data'; + +describe('[Metamodel][Effects] DatabaseEffects', () => { + let actions = new Observable(); + let effects: DatabaseEffects; + let metadata: EffectsMetadata<DatabaseEffects>; + let service: DatabaseService; + let toastr: ToastrService; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + DatabaseEffects, + { provide: DatabaseService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions) + ] + }).compileComponents(); + effects = TestBed.inject(DatabaseEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(DatabaseService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadDatabases$ effect', () => { + it('should dispatch the loadDatabaseListSuccess action on success', () => { + const action = databaseActions.loadDatabaseList(); + const outcome = databaseActions.loadDatabaseListSuccess({ databases: DATABASE_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATABASE_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveDatabaseList = jest.fn(() => response); + + expect(effects.loadDatabases$).toBeObservable(expected); + }); + + it('should dispatch the loadDatabaseListFail action on HTTP failure', () => { + const action = databaseActions.loadDatabaseList(); + const error = new Error(); + const outcome = databaseActions.loadDatabaseListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveDatabaseList = jest.fn(() => response); + + expect(effects.loadDatabases$).toBeObservable(expected); + }); + }); + + describe('addDatabase$ effect', () => { + it('should dispatch the addDatabaseSuccess action on success', () => { + const action = databaseActions.addDatabase({ database: DATABASE }); + const outcome = databaseActions.addDatabaseSuccess({ database: DATABASE }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATABASE }); + const expected = cold('--b', { b: outcome }); + service.addDatabase = jest.fn(() => response); + + expect(effects.addDatabase$).toBeObservable(expected); + }); + + it('should dispatch the addDatabaseFail action on HTTP failure', () => { + const action = databaseActions.addDatabase({ database: DATABASE }); + const error = new Error(); + const outcome = databaseActions.addDatabaseFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.addDatabase = jest.fn(() => response); + + expect(effects.addDatabase$).toBeObservable(expected); + }); + }); + + describe('addDatabaseSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatabaseSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const routerSpy = jest.spyOn(router, 'navigate'); + const action = databaseActions.addDatabaseSuccess({ database: DATABASE }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addDatabaseSuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Database successfully added', + 'The new database was added into the database' + ); + expect(routerSpy).toHaveBeenCalledTimes(1); + expect(routerSpy).toHaveBeenCalledWith(['/admin/database/database-list']); + }); + }); + + describe('addDatabaseFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatabaseFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = databaseActions.addDatabaseFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addDatabaseFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to add database', + 'The new database could not be added into the database' + ); + }); + }); + + describe('editDatabase$ effect', () => { + it('should dispatch the editDatabaseSuccess action on success', () => { + const action = databaseActions.editDatabase({ database: DATABASE }); + const outcome = databaseActions.editDatabaseSuccess({ database: DATABASE }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATABASE }); + const expected = cold('--b', { b: outcome }); + service.editDatabase = jest.fn(() => response); + + expect(effects.editDatabase$).toBeObservable(expected); + }); + + it('should dispatch the editDatabaseFail action on HTTP failure', () => { + const action = databaseActions.editDatabase({ database: DATABASE }); + const error = new Error(); + const outcome = databaseActions.editDatabaseFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.editDatabase = jest.fn(() => response); + + expect(effects.editDatabase$).toBeObservable(expected); + }); + }); + + describe('editDatabaseSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatabaseSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const routerSpy = jest.spyOn(router, 'navigate'); + const action = databaseActions.editDatabaseSuccess({ database: DATABASE }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editDatabaseSuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Database successfully edited', + 'The existing database has been edited into the database' + ); + expect(routerSpy).toHaveBeenCalledTimes(1); + expect(routerSpy).toHaveBeenCalledWith(['/admin/database/database-list']); + }); + }); + + describe('editDatabaseFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatabaseFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = databaseActions.editDatabaseFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editDatabaseFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to edit database', + 'The existing database could not be edited into the database' + ); + }); + }); + + describe('deleteDatabase$ effect', () => { + it('should dispatch the deleteDatabaseSuccess action on success', () => { + const action = databaseActions.deleteDatabase({ database: DATABASE }); + const outcome = databaseActions.deleteDatabaseSuccess({ database: DATABASE }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATABASE }); + const expected = cold('--b', { b: outcome }); + service.deleteDatabase = jest.fn(() => response); + + expect(effects.deleteDatabase$).toBeObservable(expected); + }); + + it('should dispatch the deleteDatabaseFail action on HTTP failure', () => { + const action = databaseActions.deleteDatabase({ database: DATABASE }); + const error = new Error(); + const outcome = databaseActions.deleteDatabaseFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.deleteDatabase = jest.fn(() => response); + + expect(effects.deleteDatabase$).toBeObservable(expected); + }); + }); + + describe('deleteDatabaseSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatabaseSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const action = databaseActions.deleteDatabaseSuccess({ database: DATABASE }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatabaseSuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Database successfully deleted', + 'The existing database has been deleted' + ); + }); + }); + + describe('deleteDatabaseFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatabaseFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = databaseActions.deleteDatabaseFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatabaseFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to delete database', + 'The existing database could not be deleted from the database' + ); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/database.effects.ts b/client/src/app/metamodel/effects/database.effects.ts index 0d2b2add88347833596b69248b8edf08bd5737e7..7be7fa4755703a9518afc3007ff579f64c930aec 100644 --- a/client/src/app/metamodel/effects/database.effects.ts +++ b/client/src/app/metamodel/effects/database.effects.ts @@ -17,10 +17,18 @@ import { ToastrService } from 'ngx-toastr'; import * as databaseActions from '../actions/database.actions'; import { DatabaseService } from '../services/database.service'; - + +/** + * @class + * @classdesc Database effects. + */ @Injectable() export class DatabaseEffects { - loadDatabases$ = createEffect(() => + + /** + * Calls action to retrieve database list. + */ + loadDatabases$ = createEffect((): any => this.actions$.pipe( ofType(databaseActions.loadDatabaseList), mergeMap(() => this.databaseService.retrieveDatabaseList() @@ -32,7 +40,10 @@ export class DatabaseEffects { ) ); - addDatabase$ = createEffect(() => + /** + * Calls action to add a database. + */ + addDatabase$ = createEffect((): any => this.actions$.pipe( ofType(databaseActions.addDatabase), mergeMap(action => this.databaseService.addDatabase(action.database) @@ -44,24 +55,33 @@ export class DatabaseEffects { ) ); - addDatabaseSuccess$ = createEffect(() => + /** + * Displays add database success notification. + */ + addDatabaseSuccess$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.addDatabaseSuccess), tap(() => { this.router.navigate(['/admin/database/database-list']); this.toastr.success('Database successfully added', 'The new database was added into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - addDatabaseFail$ = createEffect(() => + /** + * Displays add database error notification. + */ + addDatabaseFail$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.addDatabaseFail), tap(() => this.toastr.error('Failure to add database', 'The new database could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editDatabase$ = createEffect(() => + /** + * Calls action to modify a database. + */ + editDatabase$ = createEffect((): any => this.actions$.pipe( ofType(databaseActions.editDatabase), mergeMap(action => this.databaseService.editDatabase(action.database) @@ -73,24 +93,33 @@ export class DatabaseEffects { ) ); - editDatabaseSuccess$ = createEffect(() => + /** + * Displays edit database success notification. + */ + editDatabaseSuccess$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.editDatabaseSuccess), tap(() => { this.router.navigate(['/admin/database/database-list']); this.toastr.success('Database successfully edited', 'The existing database has been edited into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - editDatabaseFail$ = createEffect(() => + /** + * Displays edit database error notification. + */ + editDatabaseFail$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.editDatabaseFail), tap(() => this.toastr.error('Failure to edit database', 'The existing database could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDatabase$ = createEffect(() => + /** + * Calls action to remove a database. + */ + deleteDatabase$ = createEffect((): any => this.actions$.pipe( ofType(databaseActions.deleteDatabase), mergeMap(action => this.databaseService.deleteDatabase(action.database.id) @@ -102,20 +131,26 @@ export class DatabaseEffects { ) ); - deleteDatabaseSuccess$ = createEffect(() => + /** + * Displays delete database success notification. + */ + deleteDatabaseSuccess$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.deleteDatabaseSuccess), tap(() => this.toastr.success('Database successfully deleted', 'The existing database has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDatabaseFail$ = createEffect(() => + /** + * Displays delete database error notification. + */ + deleteDatabaseFail$ = createEffect(() => this.actions$.pipe( ofType(databaseActions.deleteDatabaseFail), tap(() => this.toastr.error('Failure to delete database', 'The existing database could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private databaseService: DatabaseService, diff --git a/client/src/app/metamodel/effects/dataset-family.effects.spec.ts b/client/src/app/metamodel/effects/dataset-family.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0276a8e0609847403dab7888cc7202d7746f618 --- /dev/null +++ b/client/src/app/metamodel/effects/dataset-family.effects.spec.ts @@ -0,0 +1,317 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { DatasetFamilyEffects } from './dataset-family.effects'; +import { DatasetFamilyService } from '../services/dataset-family.service'; +import * as datasetFamilyActions from '../actions/dataset-family.actions'; +import * as instanceSelector from '../selectors/instance.selector'; +import { DATASET_FAMILY_LIST, DATASET_FAMILY } from '../../../test-data'; + +describe('[Metamodel][Effects] DatasetFamilyEffects', () => { + let actions = new Observable(); + let effects: DatasetFamilyEffects; + let metadata: EffectsMetadata<DatasetFamilyEffects>; + let service: DatasetFamilyService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { metamodel: { } }; + let mockInstanceSelectorSelectInstanceNameByRoute; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + DatasetFamilyEffects, + { provide: DatasetFamilyService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions), + provideMockStore({ initialState }) + ] + }).compileComponents(); + effects = TestBed.inject(DatasetFamilyEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(DatasetFamilyService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + store = TestBed.inject(MockStore); + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'' + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadDatasetFamilies$ effect', () => { + it('should dispatch the loadDatasetFamilyListSuccess action on success', () => { + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'myInstance' + ); + + const action = datasetFamilyActions.loadDatasetFamilyList(); + const outcome = datasetFamilyActions.loadDatasetFamilyListSuccess({ datasetFamilies: DATASET_FAMILY_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET_FAMILY_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveDatasetFamilyList = jest.fn(() => response); + + expect(effects.loadDatasetFamilies$).toBeObservable(expected); + expect(service.retrieveDatasetFamilyList).toHaveBeenCalledWith('myInstance'); + }); + + it('should dispatch the loadDatasetFamilyListFail action on HTTP failure', () => { + const action = datasetFamilyActions.loadDatasetFamilyList(); + const error = new Error(); + const outcome = datasetFamilyActions.loadDatasetFamilyListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveDatasetFamilyList = jest.fn(() => response); + + expect(effects.loadDatasetFamilies$).toBeObservable(expected); + }); + }); + + describe('addDatasetFamily$ effect', () => { + it('should dispatch the addDatasetFamilySuccess action on success', () => { + const action = datasetFamilyActions.addDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const outcome = datasetFamilyActions.addDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.addDatasetFamily = jest.fn(() => response); + + expect(effects.addDatasetFamily$).toBeObservable(expected); + }); + + it('should dispatch the addDatasetFamilyFail action on HTTP failure', () => { + const action = datasetFamilyActions.addDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const error = new Error(); + const outcome = datasetFamilyActions.addDatasetFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.addDatasetFamily = jest.fn(() => response); + + expect(effects.addDatasetFamily$).toBeObservable(expected); + }); + }); + + describe('addDatasetFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatasetFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + // instanceSelector.selectInstanceNameByRoute,'myInstance' + // ); + // + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = datasetFamilyActions.addDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.addDatasetFamilySuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Dataset family successfully added', + // 'The new dataset family was added into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/instance/configure-instance/myInstance/datasetFamily']); + // }); + }); + + describe('addDatasetFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatasetFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetFamilyActions.addDatasetFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addDatasetFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to add dataset family', + 'The new dataset family could not be added into the database' + ); + }); + }); + + describe('editDatasetFamily$ effect', () => { + it('should dispatch the editDatasetFamilySuccess action on success', () => { + const action = datasetFamilyActions.editDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const outcome = datasetFamilyActions.editDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.editDatasetFamily = jest.fn(() => response); + + expect(effects.editDatasetFamily$).toBeObservable(expected); + }); + + it('should dispatch the editDatasetFamilyFail action on HTTP failure', () => { + const action = datasetFamilyActions.editDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const error = new Error(); + const outcome = datasetFamilyActions.editDatasetFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.editDatasetFamily = jest.fn(() => response); + + expect(effects.editDatasetFamily$).toBeObservable(expected); + }); + }); + + describe('editDatasetFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatasetFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = datasetFamilyActions.editDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.editDatasetFamilySuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Dataset family successfully edited', + // 'The existing dataset family has been edited into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/datasetFamily/datasetFamily-list']); + // }); + }); + + describe('editDatasetFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatasetFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetFamilyActions.editDatasetFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editDatasetFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to edit dataset family', + 'The existing dataset family could not be edited into the database' + ); + }); + }); + + describe('deleteDatasetFamily$ effect', () => { + it('should dispatch the deleteDatasetFamilySuccess action on success', () => { + const action = datasetFamilyActions.deleteDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const outcome = datasetFamilyActions.deleteDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET_FAMILY }); + const expected = cold('--b', { b: outcome }); + service.deleteDatasetFamily = jest.fn(() => response); + + expect(effects.deleteDatasetFamily$).toBeObservable(expected); + }); + + it('should dispatch the deleteDatasetFamilyFail action on HTTP failure', () => { + const action = datasetFamilyActions.deleteDatasetFamily({ datasetFamily: DATASET_FAMILY }); + const error = new Error(); + const outcome = datasetFamilyActions.deleteDatasetFamilyFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.deleteDatasetFamily = jest.fn(() => response); + + expect(effects.deleteDatasetFamily$).toBeObservable(expected); + }); + }); + + describe('deleteDatasetFamilySuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatasetFamilySuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const action = datasetFamilyActions.deleteDatasetFamilySuccess({ datasetFamily: DATASET_FAMILY }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatasetFamilySuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Dataset family successfully deleted', + 'The existing dataset family has been deleted' + ); + }); + }); + + describe('deleteDatasetFamilyFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatasetFamilyFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetFamilyActions.deleteDatasetFamilyFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatasetFamilyFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to delete dataset family', + 'The existing dataset family could not be deleted from the database' + ); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/dataset-family.effects.ts b/client/src/app/metamodel/effects/dataset-family.effects.ts index c3aa0227097b011558ff28218103a39ccf42e5c4..c2a3925a8b491b96191dc568a3bf08093c49a27a 100644 --- a/client/src/app/metamodel/effects/dataset-family.effects.ts +++ b/client/src/app/metamodel/effects/dataset-family.effects.ts @@ -19,10 +19,19 @@ import { ToastrService } from 'ngx-toastr'; import * as datasetFamilyActions from '../actions/dataset-family.actions'; import { DatasetFamilyService } from '../services/dataset-family.service'; import * as instanceSelector from '../selectors/instance.selector'; - + + +/** + * @class + * @classdesc Dataset family effects. + */ @Injectable() export class DatasetFamilyEffects { - loadDatasetFamilys$ = createEffect(() => + + /** + * Calls action to retrieve dataset family list. + */ + loadDatasetFamilies$ = createEffect((): any => this.actions$.pipe( ofType(datasetFamilyActions.loadDatasetFamilyList), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -35,7 +44,10 @@ export class DatasetFamilyEffects { ) ); - addDatasetFamily$ = createEffect(() => + /** + * Calls action to add a dataset family. + */ + addDatasetFamily$ = createEffect((): any => this.actions$.pipe( ofType(datasetFamilyActions.addDatasetFamily), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -48,7 +60,10 @@ export class DatasetFamilyEffects { ) ); - addDatasetFamilySuccess$ = createEffect(() => + /** + * Displays add dataset family success notification. + */ + addDatasetFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.addDatasetFamilySuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -56,17 +71,23 @@ export class DatasetFamilyEffects { this.router.navigate([`/admin/configure-instance/${instanceName}`]); this.toastr.success('Dataset family successfully added', 'The new dataset family was added into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - addDatasetFamilyFail$ = createEffect(() => + /** + * Displays add dataset family fail notification. + */ + addDatasetFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.addDatasetFamilyFail), tap(() => this.toastr.error('Failure to add dataset family', 'The new dataset family could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editDatasetFamily$ = createEffect(() => + /** + * Calls action to modify a dataset family. + */ + editDatasetFamily$ = createEffect((): any => this.actions$.pipe( ofType(datasetFamilyActions.editDatasetFamily), mergeMap(action => this.datasetFamilyService.editDatasetFamily(action.datasetFamily) @@ -78,7 +99,10 @@ export class DatasetFamilyEffects { ) ); - editDatasetFamilySuccess$ = createEffect(() => + /** + * Displays edit dataset family success notification. + */ + editDatasetFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.editDatasetFamilySuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -86,17 +110,23 @@ export class DatasetFamilyEffects { this.router.navigate([`/admin/configure-instance/${instanceName}`]); this.toastr.success('Dataset family successfully edited', 'The existing dataset family has been edited into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - editDatasetFamilyFail$ = createEffect(() => + /** + * Displays edit dataset family error notification. + */ + editDatasetFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.editDatasetFamilyFail), tap(() => this.toastr.error('Failure to edit dataset family', 'The existing dataset family could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDatasetFamily$ = createEffect(() => + /** + * Calls action to remove a dataset family. + */ + deleteDatasetFamily$ = createEffect((): any => this.actions$.pipe( ofType(datasetFamilyActions.deleteDatasetFamily), mergeMap(action => this.datasetFamilyService.deleteDatasetFamily(action.datasetFamily.id) @@ -108,20 +138,26 @@ export class DatasetFamilyEffects { ) ); - deleteDatasetFamilySuccess$ = createEffect(() => + /** + * Displays delete dataset family success notification. + */ + deleteDatasetFamilySuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.deleteDatasetFamilySuccess), tap(() => this.toastr.success('Dataset family successfully deleted', 'The existing dataset family has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDatasetFamilyFail$ = createEffect(() => + /** + * Displays delete dataset family error notification. + */ + deleteDatasetFamilyFail$ = createEffect(() => this.actions$.pipe( ofType(datasetFamilyActions.deleteDatasetFamilyFail), tap(() => this.toastr.error('Failure to delete dataset family', 'The existing dataset family could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private datasetFamilyService: DatasetFamilyService, diff --git a/client/src/app/metamodel/effects/dataset.effects.spec.ts b/client/src/app/metamodel/effects/dataset.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..119200431817702c361a4505c21032c3b6754209 --- /dev/null +++ b/client/src/app/metamodel/effects/dataset.effects.spec.ts @@ -0,0 +1,317 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { DatasetEffects } from './dataset.effects'; +import { DatasetService } from '../services/dataset.service'; +import * as datasetActions from '../actions/dataset.actions'; +import * as instanceSelector from '../selectors/instance.selector'; +import { DATASET, DATASET_LIST } from '../../../test-data'; + +describe('[Metamodel][Effects] DatasetEffects', () => { + let actions = new Observable(); + let effects: DatasetEffects; + let metadata: EffectsMetadata<DatasetEffects>; + let service: DatasetService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { metamodel: { } }; + let mockInstanceSelectorSelectInstanceNameByRoute; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + DatasetEffects, + { provide: DatasetService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions), + provideMockStore({ initialState }) + ] + }).compileComponents(); + effects = TestBed.inject(DatasetEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(DatasetService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + store = TestBed.inject(MockStore); + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'' + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadDatasets$ effect', () => { + it('should dispatch the loadDatasetListSuccess action on success', () => { + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'myInstance' + ); + + const action = datasetActions.loadDatasetList(); + const outcome = datasetActions.loadDatasetListSuccess({ datasets: DATASET_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveDatasetList = jest.fn(() => response); + + expect(effects.loadDatasets$).toBeObservable(expected); + expect(service.retrieveDatasetList).toHaveBeenCalledWith('myInstance'); + }); + + it('should dispatch the loadDatasetListFail action on HTTP failure', () => { + const action = datasetActions.loadDatasetList(); + const error = new Error(); + const outcome = datasetActions.loadDatasetListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveDatasetList = jest.fn(() => response); + + expect(effects.loadDatasets$).toBeObservable(expected); + }); + }); + + describe('addDataset$ effect', () => { + it('should dispatch the addDatasetSuccess action on success', () => { + const action = datasetActions.addDataset({ dataset: DATASET }); + const outcome = datasetActions.addDatasetSuccess({ dataset: DATASET }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET }); + const expected = cold('--b', { b: outcome }); + service.addDataset = jest.fn(() => response); + + expect(effects.addDataset$).toBeObservable(expected); + }); + + it('should dispatch the addDatasetFail action on HTTP failure', () => { + const action = datasetActions.addDataset({ dataset: DATASET }); + const error = new Error(); + const outcome = datasetActions.addDatasetFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.addDataset = jest.fn(() => response); + + expect(effects.addDataset$).toBeObservable(expected); + }); + }); + + describe('addDatasetSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatasetSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + // instanceSelector.selectInstanceNameByRoute,'myInstance' + // ); + // + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = datasetActions.addDatasetSuccess({ dataset: DATASET }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.addDatasetSuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Dataset successfully added', + // 'The new dataset was added into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/instance/configure-instance/myInstance/dataset']); + // }); + }); + + describe('addDatasetFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addDatasetFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetActions.addDatasetFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addDatasetFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to add dataset', + 'The new dataset could not be added into the database' + ); + }); + }); + + describe('editDataset$ effect', () => { + it('should dispatch the editDatasetSuccess action on success', () => { + const action = datasetActions.editDataset({ dataset: DATASET }); + const outcome = datasetActions.editDatasetSuccess({ dataset: DATASET }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET }); + const expected = cold('--b', { b: outcome }); + service.editDataset = jest.fn(() => response); + + expect(effects.editDataset$).toBeObservable(expected); + }); + + it('should dispatch the editDatasetFail action on HTTP failure', () => { + const action = datasetActions.editDataset({ dataset: DATASET }); + const error = new Error(); + const outcome = datasetActions.editDatasetFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.editDataset = jest.fn(() => response); + + expect(effects.editDataset$).toBeObservable(expected); + }); + }); + + describe('editDatasetSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatasetSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = datasetActions.editDatasetSuccess({ dataset: DATASET }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.editDatasetSuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Dataset successfully edited', + // 'The existing dataset has been edited into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/dataset/dataset-list']); + // }); + }); + + describe('editDatasetFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editDatasetFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetActions.editDatasetFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editDatasetFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to edit dataset', + 'The existing dataset could not be edited into the database' + ); + }); + }); + + describe('deleteDataset$ effect', () => { + it('should dispatch the deleteDatasetSuccess action on success', () => { + const action = datasetActions.deleteDataset({ dataset: DATASET }); + const outcome = datasetActions.deleteDatasetSuccess({ dataset: DATASET }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: DATASET }); + const expected = cold('--b', { b: outcome }); + service.deleteDataset = jest.fn(() => response); + + expect(effects.deleteDataset$).toBeObservable(expected); + }); + + it('should dispatch the deleteDatasetFail action on HTTP failure', () => { + const action = datasetActions.deleteDataset({ dataset: DATASET }); + const error = new Error(); + const outcome = datasetActions.deleteDatasetFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.deleteDataset = jest.fn(() => response); + + expect(effects.deleteDataset$).toBeObservable(expected); + }); + }); + + describe('deleteDatasetSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatasetSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const action = datasetActions.deleteDatasetSuccess({ dataset: DATASET }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatasetSuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Dataset successfully deleted', + 'The existing dataset has been deleted' + ); + }); + }); + + describe('deleteDatasetFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteDatasetFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = datasetActions.deleteDatasetFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteDatasetFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to delete dataset', + 'The existing dataset could not be deleted from the database' + ); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/dataset.effects.ts b/client/src/app/metamodel/effects/dataset.effects.ts index cf0a0e3fee30c9f2b1179ee4993af68ed3e350c0..28894b6ffbe6c4b407f4ce2da36bc49897755b0c 100644 --- a/client/src/app/metamodel/effects/dataset.effects.ts +++ b/client/src/app/metamodel/effects/dataset.effects.ts @@ -19,10 +19,18 @@ import { ToastrService } from 'ngx-toastr'; import * as datasetActions from '../actions/dataset.actions'; import { DatasetService } from '../services/dataset.service'; import * as instanceSelector from '../selectors/instance.selector'; - + +/** + * @class + * @classdesc Dataset effects. + */ @Injectable() export class DatasetEffects { - loadDatasets$ = createEffect(() => + + /** + * Calls action to retrieve dataset list. + */ + loadDatasets$ = createEffect((): any => this.actions$.pipe( ofType(datasetActions.loadDatasetList), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -35,7 +43,10 @@ export class DatasetEffects { ) ); - addDataset$ = createEffect(() => + /** + * Calls action to add a dataset. + */ + addDataset$ = createEffect((): any => this.actions$.pipe( ofType(datasetActions.addDataset), mergeMap(action => this.datasetService.addDataset(action.dataset) @@ -47,7 +58,10 @@ export class DatasetEffects { ) ); - addDatasetSuccess$ = createEffect(() => + /** + * Displays add dataset success notification. + */ + addDatasetSuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.addDatasetSuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -55,17 +69,23 @@ export class DatasetEffects { this.router.navigate([`/admin/instance/configure-instance/${instanceName}`]); this.toastr.success('Dataset successfully added', 'The new dataset was added into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - addDatasetFail$ = createEffect(() => + /** + * Displays add dataset error notification. + */ + addDatasetFail$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.addDatasetFail), tap(() => this.toastr.error('Failure to add dataset', 'The new dataset could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editDataset$ = createEffect(() => + /** + * Calls action to add a dataset. + */ + editDataset$ = createEffect((): any => this.actions$.pipe( ofType(datasetActions.editDataset), mergeMap(action => this.datasetService.editDataset(action.dataset) @@ -77,7 +97,10 @@ export class DatasetEffects { ) ); - editDatasetSuccess$ = createEffect(() => + /** + * Displays modify dataset success notification. + */ + editDatasetSuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.editDatasetSuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -85,17 +108,23 @@ export class DatasetEffects { this.router.navigate([`/admin/instance/configure-instance/${instanceName}`]); this.toastr.success('Dataset successfully edited', 'The existing dataset has been edited into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - editDatasetFail$ = createEffect(() => + /** + * Displays modify dataset error notification. + */ + editDatasetFail$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.editDatasetFail), tap(() => this.toastr.error('Failure to edit dataset', 'The existing dataset could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDataset$ = createEffect(() => + /** + * Calls action to add a dataset. + */ + deleteDataset$ = createEffect((): any => this.actions$.pipe( ofType(datasetActions.deleteDataset), mergeMap(action => this.datasetService.deleteDataset(action.dataset.name) @@ -107,20 +136,26 @@ export class DatasetEffects { ) ); - deleteDatasetSuccess$ = createEffect(() => + /** + * Displays remove dataset success notification. + */ + deleteDatasetSuccess$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.deleteDatasetSuccess), tap(() => this.toastr.success('Dataset successfully deleted', 'The existing dataset has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteDatasetFail$ = createEffect(() => + /** + * Displays remove dataset error notification. + */ + deleteDatasetFail$ = createEffect(() => this.actions$.pipe( ofType(datasetActions.deleteDatasetFail), tap(() => this.toastr.error('Failure to delete dataset', 'The existing dataset could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private datasetService: DatasetService, diff --git a/client/src/app/metamodel/effects/group.effects.spec.ts b/client/src/app/metamodel/effects/group.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..80141d989e4478d0cea86487066e419e927b09d2 --- /dev/null +++ b/client/src/app/metamodel/effects/group.effects.spec.ts @@ -0,0 +1,317 @@ +import { TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { GroupEffects } from './group.effects'; +import { GroupService } from '../services/group.service'; +import * as groupActions from '../actions/group.actions'; +import * as instanceSelector from '../selectors/instance.selector'; +import { GROUP, GROUP_LIST } from '../../../test-data'; + +describe('[Metamodel][Effects] GroupEffects', () => { + let actions = new Observable(); + let effects: GroupEffects; + let metadata: EffectsMetadata<GroupEffects>; + let service: GroupService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { metamodel: { } }; + let mockInstanceSelectorSelectInstanceNameByRoute; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + GroupEffects, + { provide: GroupService, useValue: { }}, + { provide: Router, useValue: { navigate: jest.fn() }}, + { provide: ToastrService, useValue: { + success: jest.fn(), + error: jest.fn() + }}, + provideMockActions(() => actions), + provideMockStore({ initialState }) + ] + }).compileComponents(); + effects = TestBed.inject(GroupEffects); + metadata = getEffectsMetadata(effects); + service = TestBed.inject(GroupService); + toastr = TestBed.inject(ToastrService); + router = TestBed.inject(Router); + store = TestBed.inject(MockStore); + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'' + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('loadGroups$ effect', () => { + it('should dispatch the loadGroupListSuccess action on success', () => { + mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + instanceSelector.selectInstanceNameByRoute,'myInstance' + ); + + const action = groupActions.loadGroupList(); + const outcome = groupActions.loadGroupListSuccess({ groups: GROUP_LIST }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: GROUP_LIST }); + const expected = cold('--b', { b: outcome }); + service.retrieveGroupList = jest.fn(() => response); + + expect(effects.loadGroups$).toBeObservable(expected); + expect(service.retrieveGroupList).toHaveBeenCalledWith('myInstance'); + }); + + it('should dispatch the loadGroupListFail action on HTTP failure', () => { + const action = groupActions.loadGroupList(); + const error = new Error(); + const outcome = groupActions.loadGroupListFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.retrieveGroupList = jest.fn(() => response); + + expect(effects.loadGroups$).toBeObservable(expected); + }); + }); + + describe('addGroup$ effect', () => { + it('should dispatch the addGroupSuccess action on success', () => { + const action = groupActions.addGroup({ group: GROUP }); + const outcome = groupActions.addGroupSuccess({ group: GROUP }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: GROUP }); + const expected = cold('--b', { b: outcome }); + service.addGroup = jest.fn(() => response); + + expect(effects.addGroup$).toBeObservable(expected); + }); + + it('should dispatch the addGroupFail action on HTTP failure', () => { + const action = groupActions.addGroup({ group: GROUP }); + const error = new Error(); + const outcome = groupActions.addGroupFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.addGroup = jest.fn(() => response); + + expect(effects.addGroup$).toBeObservable(expected); + }); + }); + + describe('addGroupSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addGroupSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // mockInstanceSelectorSelectInstanceNameByRoute = store.overrideSelector( + // instanceSelector.selectInstanceNameByRoute,'myInstance' + // ); + // + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = groupActions.addGroupSuccess({ group: GROUP }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.addGroupSuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Group successfully added', + // 'The new group was added into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/instance/configure-instance/myInstance/group']); + // }); + }); + + describe('addGroupFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.addGroupFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = groupActions.addGroupFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.addGroupFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to add group', + 'The new group could not be added into the database' + ); + }); + }); + + describe('editGroup$ effect', () => { + it('should dispatch the editGroupSuccess action on success', () => { + const action = groupActions.editGroup({ group: GROUP }); + const outcome = groupActions.editGroupSuccess({ group: GROUP }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: GROUP }); + const expected = cold('--b', { b: outcome }); + service.editGroup = jest.fn(() => response); + + expect(effects.editGroup$).toBeObservable(expected); + }); + + it('should dispatch the editGroupFail action on HTTP failure', () => { + const action = groupActions.editGroup({ group: GROUP }); + const error = new Error(); + const outcome = groupActions.editGroupFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.editGroup = jest.fn(() => response); + + expect(effects.editGroup$).toBeObservable(expected); + }); + }); + + describe('editGroupSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editGroupSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + // it('should display a success notification', () => { + // const toastrSpy = jest.spyOn(toastr, 'success'); + // const routerSpy = jest.spyOn(router, 'navigate'); + // const action = groupActions.editGroupSuccess({ group: GROUP }); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.editGroupSuccess$).toBeObservable(expected); + // expect(toastrSpy).toHaveBeenCalledTimes(1); + // expect(toastrSpy).toHaveBeenCalledWith( + // 'Group successfully edited', + // 'The existing group has been edited into the database' + // ); + // expect(routerSpy).toHaveBeenCalledTimes(1); + // expect(routerSpy).toHaveBeenCalledWith(['/admin/group/group-list']); + // }); + }); + + describe('editGroupFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.editGroupFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = groupActions.editGroupFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.editGroupFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to edit group', + 'The existing group could not be edited into the database' + ); + }); + }); + + describe('deleteGroup$ effect', () => { + it('should dispatch the deleteGroupSuccess action on success', () => { + const action = groupActions.deleteGroup({ group: GROUP }); + const outcome = groupActions.deleteGroupSuccess({ group: GROUP }); + + actions = hot('-a', { a: action }); + const response = cold('-a|', { a: GROUP }); + const expected = cold('--b', { b: outcome }); + service.deleteGroup = jest.fn(() => response); + + expect(effects.deleteGroup$).toBeObservable(expected); + }); + + it('should dispatch the deleteGroupFail action on HTTP failure', () => { + const action = groupActions.deleteGroup({ group: GROUP }); + const error = new Error(); + const outcome = groupActions.deleteGroupFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', { }, error); + const expected = cold('--b', { b: outcome }); + service.deleteGroup = jest.fn(() => response); + + expect(effects.deleteGroup$).toBeObservable(expected); + }); + }); + + describe('deleteGroupSuccess$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteGroupSuccess$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a success notification', () => { + const toastrSpy = jest.spyOn(toastr, 'success'); + const action = groupActions.deleteGroupSuccess({ group: GROUP }); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteGroupSuccess$).toBeObservable(expected); + expect(toastrSpy).toHaveBeenCalledTimes(1); + expect(toastrSpy).toHaveBeenCalledWith( + 'Group successfully deleted', + 'The existing group has been deleted' + ); + }); + }); + + describe('deleteGroupFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.deleteGroupFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display an error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = groupActions.deleteGroupFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.deleteGroupFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith( + 'Failure to delete group', + 'The existing group could not be deleted from the database' + ); + }); + }); +}); diff --git a/client/src/app/metamodel/effects/group.effects.ts b/client/src/app/metamodel/effects/group.effects.ts index 6f3567afd9e0a71550dfba4713df71b5cf69a80d..f9001e0764849b1d33e13ed8e756ef1d96dddafc 100644 --- a/client/src/app/metamodel/effects/group.effects.ts +++ b/client/src/app/metamodel/effects/group.effects.ts @@ -19,10 +19,18 @@ import { ToastrService } from 'ngx-toastr'; import * as groupActions from '../actions/group.actions'; import { GroupService } from '../services/group.service'; import * as instanceSelector from '../selectors/instance.selector'; - + +/** + * @class + * @classdesc Survey effects. + */ @Injectable() export class GroupEffects { - loadGroups$ = createEffect(() => + + /** + * Calls action to retrieve group list. + */ + loadGroups$ = createEffect((): any => this.actions$.pipe( ofType(groupActions.loadGroupList), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -35,7 +43,10 @@ export class GroupEffects { ) ); - addGroup$ = createEffect(() => + /** + * Calls action to add a group. + */ + addGroup$ = createEffect((): any => this.actions$.pipe( ofType(groupActions.addGroup), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -48,25 +59,34 @@ export class GroupEffects { ) ); - addGroupSuccess$ = createEffect(() => + /** + * Displays add group success notification. + */ + addGroupSuccess$ = createEffect(() => this.actions$.pipe( ofType(groupActions.addGroupSuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), - tap(([, instanceName]) => { + tap(instanceName => { this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/group`); this.toastr.success('Group successfully added', 'The new group was added into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - addGroupFail$ = createEffect(() => + /** + * Displays add group error notification. + */ + addGroupFail$ = createEffect(() => this.actions$.pipe( ofType(groupActions.addGroupFail), tap(() => this.toastr.error('Failure to add group', 'The new group could not be added into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - editGroup$ = createEffect(() => + /** + * Calls action to modify a group. + */ + editGroup$ = createEffect((): any => this.actions$.pipe( ofType(groupActions.editGroup), mergeMap(action => this.groupService.editGroup(action.group) @@ -78,7 +98,10 @@ export class GroupEffects { ) ); - editGroupSuccess$ = createEffect(() => + /** + * Displays edit group success notification. + */ + editGroupSuccess$ = createEffect(() => this.actions$.pipe( ofType(groupActions.editGroupSuccess), concatLatestFrom(() => this.store.select(instanceSelector.selectInstanceNameByRoute)), @@ -86,17 +109,23 @@ export class GroupEffects { this.router.navigateByUrl(`/admin/instance/configure-instance/${instanceName}/group`); this.toastr.success('Group successfully edited', 'The existing group has been edited into the database') }) - ), { dispatch: false} + ), { dispatch: false } ); - editGroupFail$ = createEffect(() => + /** + * Displays edit group error notification. + */ + editGroupFail$ = createEffect(() => this.actions$.pipe( ofType(groupActions.editGroupFail), tap(() => this.toastr.error('Failure to edit group', 'The existing group could not be edited into the database')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteGroup$ = createEffect(() => + /** + * Calls action to remove a group. + */ + deleteGroup$ = createEffect((): any => this.actions$.pipe( ofType(groupActions.deleteGroup), mergeMap(action => this.groupService.deleteGroup(action.group.id) @@ -108,20 +137,26 @@ export class GroupEffects { ) ); - deleteGroupSuccess$ = createEffect(() => + /** + * Displays delete group success notification. + */ + deleteGroupSuccess$ = createEffect(() => this.actions$.pipe( ofType(groupActions.deleteGroupSuccess), tap(() => this.toastr.success('Group successfully deleted', 'The existing group has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); - deleteGroupFail$ = createEffect(() => + /** + * Displays delete group error notification. + */ + deleteGroupFail$ = createEffect(() => this.actions$.pipe( ofType(groupActions.deleteGroupFail), tap(() => this.toastr.error('Failure to delete group', 'The existing group could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); - + constructor( private actions$: Actions, private groupService: GroupService, diff --git a/client/src/app/metamodel/effects/output-category.effects.spec.ts b/client/src/app/metamodel/effects/output-category.effects.spec.ts index ee3f625d6994a42da5b0e03fd743dc1ecfc997af..770166a623856441a5c7b0f17b073cdbe55c91c3 100644 --- a/client/src/app/metamodel/effects/output-category.effects.spec.ts +++ b/client/src/app/metamodel/effects/output-category.effects.spec.ts @@ -2,15 +2,15 @@ import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; import { Observable } from 'rxjs'; import { cold, hot } from 'jasmine-marbles'; -import { ToastrService } from 'ngx-toastr'; +import { ToastrService } from 'ngx-toastr'; import { OutputCategoryEffects } from './output-category.effects'; import { OutputCategoryService } from '../services/output-category.service'; import * as outputCategoryActions from '../actions/output-category.actions'; -import { MockStore, provideMockStore } from '@ngrx/store/testing'; import * as datasetSelector from '../selectors/dataset.selector'; import { CATEGORY, CATEGORY_LIST } from '../../../test-data'; diff --git a/client/src/app/metamodel/effects/output-family.effects.spec.ts b/client/src/app/metamodel/effects/output-family.effects.spec.ts index de18e0327eeea31914944fa0b122b2fee0b59936..267056d81f2d00f9ef42926d741237c1fd140f9a 100644 --- a/client/src/app/metamodel/effects/output-family.effects.spec.ts +++ b/client/src/app/metamodel/effects/output-family.effects.spec.ts @@ -2,6 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { provideMockActions } from '@ngrx/effects/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; import { Observable } from 'rxjs'; import { cold, hot } from 'jasmine-marbles'; @@ -11,7 +12,6 @@ import { OutputFamilyEffects } from './output-family.effects'; import { OutputFamilyService } from '../services/output-family.service'; import * as outputFamilyActions from '../actions/output-family.actions'; import { OUTPUT_FAMILY, OUTPUT_FAMILY_LIST } from '../../../test-data'; -import { MockStore, provideMockStore } from '@ngrx/store/testing'; import * as datasetSelector from '../selectors/dataset.selector'; describe('[Metamodel][Effects] OutputFamilyEffects', () => { @@ -86,7 +86,7 @@ describe('[Metamodel][Effects] OutputFamilyEffects', () => { }); }); - describe('addOutputFamilies$ effect', () => { + describe('addOutputFamily$ effect', () => { it('should dispatch the addOutputFamilySuccess action on success', () => { mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( datasetSelector.selectDatasetNameByRoute, 'myDataset' @@ -100,7 +100,7 @@ describe('[Metamodel][Effects] OutputFamilyEffects', () => { const expected = cold('--b', { b: outcome }); service.addOutputFamily = jest.fn(() => response); - expect(effects.addOutputFamilies$).toBeObservable(expected); + expect(effects.addOutputFamily$).toBeObservable(expected); expect(service.addOutputFamily).toHaveBeenCalledWith('myDataset', OUTPUT_FAMILY); }); @@ -114,7 +114,7 @@ describe('[Metamodel][Effects] OutputFamilyEffects', () => { const expected = cold('--b', { b: outcome }); service.addOutputFamily = jest.fn(() => response); - expect(effects.addOutputFamilies$).toBeObservable(expected); + expect(effects.addOutputFamily$).toBeObservable(expected); }); }); diff --git a/client/src/app/metamodel/effects/output-family.effects.ts b/client/src/app/metamodel/effects/output-family.effects.ts index c714657b7cb7d093becc2d7a83f6c875aa6c4dfc..dfe82c0babd7a66cc87862c7678ec14bf5e4ea03 100644 --- a/client/src/app/metamodel/effects/output-family.effects.ts +++ b/client/src/app/metamodel/effects/output-family.effects.ts @@ -45,7 +45,7 @@ export class OutputFamilyEffects { /** * Calls action to add an output family to the given dataset. */ - addOutputFamilies$ = createEffect((): any => + addOutputFamily$ = createEffect((): any => this.actions$.pipe( ofType(outputFamilyActions.addOutputFamily), concatLatestFrom(() => this.store.select(datasetSelector.selectDatasetNameByRoute)), @@ -136,7 +136,7 @@ export class OutputFamilyEffects { this.actions$.pipe( ofType(outputFamilyActions.deleteOutputFamilySuccess), tap(() => this.toastr.success('Output family successfully deleted', 'The existing output family has been deleted')) - ), { dispatch: false} + ), { dispatch: false } ); /** @@ -146,7 +146,7 @@ export class OutputFamilyEffects { this.actions$.pipe( ofType(outputFamilyActions.deleteOutputFamilyFail), tap(() => this.toastr.error('Failure to delete output family', 'The existing output family could not be deleted from the database')) - ), { dispatch: false} + ), { dispatch: false } ); constructor(