diff --git a/client/src/app/instance/store/effects/search-multiple.effects.spec.ts b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed415dad1fe4868f45a89b6451b1e40a623f3d86 --- /dev/null +++ b/client/src/app/instance/store/effects/search-multiple.effects.spec.ts @@ -0,0 +1,572 @@ +import { TestBed } from '@angular/core/testing'; + +import { provideMockActions } from '@ngrx/effects/testing'; +import { EffectsMetadata, getEffectsMetadata } from '@ngrx/effects'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { Observable } from 'rxjs'; +import { cold, hot } from 'jasmine-marbles'; +import { ToastrService } from 'ngx-toastr'; + +import { SearchMultipleEffects } from './search-multiple.effects'; +import { SearchService } from '../services/search.service'; +import * as searchActions from '../actions/search.actions'; +import * as fromSearch from '../reducers/search.reducer'; +import * as fromSearchMultiple from '../reducers/search-multiple.reducer'; +import * as fromInstance from '../../../metamodel/reducers/instance.reducer'; +import * as datasetSelector from '../../../metamodel/selectors/dataset.selector'; +import * as searchSelector from '../selectors/search.selector'; +import * as attributeActions from '../../../metamodel/actions/attribute.actions'; +import * as criteriaFamilyActions from '../../../metamodel/actions/criteria-family.actions'; +import * as outputFamilyActions from '../../../metamodel/actions/output-family.actions'; +import * as outputCategoryActions from '../../../metamodel/actions/output-category.actions'; +import * as attributeSelector from '../../../metamodel/selectors/attribute.selector'; +import * as coneSearchSelector from '../selectors/cone-search.selector'; +import * as coneSearchActions from '../actions/cone-search.actions'; +import { ConeSearch, Criterion, PaginationOrder, SearchMultipleDatasetLength } from '../models'; +import * as searchMultipleSelector from '../selectors/search-multiple.selector'; +import * as instanceSelector from '../../../metamodel/selectors/instance.selector'; +import * as searchMultipleActions from '../actions/search-multiple.actions'; + +describe('SearchMultipleEffects', () => { + let actions = new Observable(); + let effects: SearchMultipleEffects; + let metadata: EffectsMetadata<SearchMultipleEffects>; + let searchService: SearchService; + let toastr: ToastrService; + let store: MockStore; + const initialState = { + metamodel: { instance: { ...fromInstance.initialState }}, + instance: { + search: {...fromSearch.initialState}, + searchMultiple: {...fromSearchMultiple.initialState} + } + }; + let mockSearchMultipleSelectorSelectPristine; + let mockSearchMultipleSelectorSelectSelectedDatasetsByRoute; + let mockSearchMultipleSelectorSelectSelectedDatasets; + let mockConeSearchSelectorSelectConeSearchByRoute; + let mockConeSearchSelectorSelectConeSearch; + let mockInstanceSelectorSelectorSelectInstanceByRouteName; + let mockDatasetSelectorSelectAllConeSearchDatasets; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + SearchMultipleEffects, + { provide: SearchService, useValue: { retrieveDataLength: jest.fn() }}, + { provide: ToastrService, useValue: { error: jest.fn() }}, + provideMockActions(() => actions), + provideMockStore({ initialState }), + ] + }).compileComponents(); + effects = TestBed.inject(SearchMultipleEffects); + metadata = getEffectsMetadata(effects); + searchService = TestBed.inject(SearchService); + toastr = TestBed.inject(ToastrService); + store = TestBed.inject(MockStore); + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,true + ); + mockSearchMultipleSelectorSelectSelectedDatasetsByRoute = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasetsByRoute,'' + ); + mockSearchMultipleSelectorSelectSelectedDatasets = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasets, [] + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'' + ); + mockConeSearchSelectorSelectConeSearch = store.overrideSelector( + coneSearchSelector.selectConeSearch,undefined + ); + mockInstanceSelectorSelectorSelectInstanceByRouteName = store.overrideSelector( + instanceSelector.selectInstanceByRouteName,undefined + ); + mockDatasetSelectorSelectAllConeSearchDatasets = store.overrideSelector( + datasetSelector.selectAllConeSearchDatasets,[] + ); + }); + + it('should be created', () => { + expect(effects).toBeTruthy(); + }); + + describe('initSearch$ effect', () => { + it('should dispatch the restartSearch action when dataset or cone search changed', () => { + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,false + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'' + ); + + const action = searchMultipleActions.initSearch(); + actions = hot('-a', { a: action }); + const expected = cold('-(bc)', { + b: coneSearchActions.deleteConeSearch(), + c: searchMultipleActions.restartSearch() + }); + + expect(effects.initSearch$).toBeObservable(expected); + }); + + it('should not dispatch action when default form parameters already loaded or no dataset selected', () => { + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,false + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'1:2:3' + ); + + const action = searchMultipleActions.initSearch(); + actions = hot('-a', { a: action }); + const expected = cold('-b', { b: { type: '[No Action] Load Default Form Parameters' } }); + + expect(effects.initSearch$).toBeObservable(expected); + }); + + it('should dispatch a bunch of actions when page is reloaded with cone search in it', () => { + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,true + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'1:2:3' + ); + mockSearchMultipleSelectorSelectSelectedDatasetsByRoute = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasetsByRoute,'' + ); + mockInstanceSelectorSelectorSelectInstanceByRouteName = store.overrideSelector( + instanceSelector.selectInstanceByRouteName, { + name: 'myInstance', + label: 'My Instance', + data_path: 'data/path', + config: { + design: { + design_color: 'green', + design_background_color: 'darker green', + design_logo: 'path/to/logo', + design_favicon: 'path/to/favicon' + }, + home: { + home_component: 'HomeComponent', + home_config: { + home_component_text: 'Description', + home_component_logo: 'path/to/logo' + } + }, + search: { + search_by_criteria_allowed: true, + search_by_criteria_label: 'Search', + search_multiple_allowed: true, + search_multiple_label: 'Search multiple', + search_multiple_all_datasets_selected: false + }, + documentation: { + documentation_allowed: true, + documentation_label: 'Documentation' + } + }, + nb_dataset_families: 1, + nb_datasets: 2 + } + ); + + const coneSearch: ConeSearch = { ra: 1, dec: 2, radius: 3 }; + + const action = searchMultipleActions.initSearch(); + actions = hot('-a', { a: action }); + const expected = cold('-(bc)', { + b: searchMultipleActions.markAsDirty(), + c: coneSearchActions.addConeSearch({ coneSearch }) + }); + + expect(effects.initSearch$).toBeObservable(expected); + }); + + it('should dispatch a bunch of actions when page is reloaded with selected datasets in it', () => { + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,true + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'' + ); + mockSearchMultipleSelectorSelectSelectedDatasetsByRoute = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasetsByRoute,'d1;d2' + ); + + const selectedDatasets: string[] = ['d1', 'd2']; + + const action = searchMultipleActions.initSearch(); + actions = hot('-a', { a: action }); + const expected = cold('-(bcd)', { + b: searchMultipleActions.markAsDirty(), + c: searchMultipleActions.updateSelectedDatasets({ selectedDatasets }), + d: searchMultipleActions.checkDatasets() + }); + + expect(effects.initSearch$).toBeObservable(expected); + }); + + it('should dispatch a bunch of actions when page is reloaded with default selected datasets', () => { + mockSearchMultipleSelectorSelectPristine = store.overrideSelector( + searchMultipleSelector.selectPristine,true + ); + mockConeSearchSelectorSelectConeSearchByRoute = store.overrideSelector( + coneSearchSelector.selectConeSearchByRoute,'' + ); + mockSearchMultipleSelectorSelectSelectedDatasetsByRoute = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasetsByRoute,'' + ); + mockInstanceSelectorSelectorSelectInstanceByRouteName = store.overrideSelector( + instanceSelector.selectInstanceByRouteName, { + name: 'myInstance', + label: 'My Instance', + data_path: 'data/path', + config: { + design: { + design_color: 'green', + design_background_color: 'darker green', + design_logo: 'path/to/logo', + design_favicon: 'path/to/favicon' + }, + home: { + home_component: 'HomeComponent', + home_config: { + home_component_text: 'Description', + home_component_logo: 'path/to/logo' + } + }, + search: { + search_by_criteria_allowed: true, + search_by_criteria_label: 'Search', + search_multiple_allowed: true, + search_multiple_label: 'Search multiple', + search_multiple_all_datasets_selected: true + }, + documentation: { + documentation_allowed: true, + documentation_label: 'Documentation' + } + }, + nb_dataset_families: 1, + nb_datasets: 2 + } + ); + mockDatasetSelectorSelectAllConeSearchDatasets = store.overrideSelector( + datasetSelector.selectAllConeSearchDatasets, [ + { + name: 'myDataset', + table_ref: '', + label: '', + description: '', + display: 1, + data_path: '', + survey_name: '', + id_dataset_family: 1, + public: true, + full_data_path: '', + config: { + images: [], + survey: { + survey_enabled: true, + survey_label: '' + }, + cone_search: { + cone_search_enabled: true, + cone_search_opened: true, + cone_search_column_ra: 1, + cone_search_column_dec: 2, + cone_search_plot_enabled: true, + cone_search_sdss_enabled: true, + cone_search_sdss_display: 1, + cone_search_background: [] + }, + download: { + download_enabled: true, + download_opened: true, + download_csv: true, + download_ascii: true, + download_vo: true, + download_archive: true + }, + summary: { + summary_enabled: true, + summary_opened: true + }, + server_link: { + server_link_enabled: true, + server_link_opened: true + }, + samp: { + samp_enabled: true, + samp_opened: true + }, + datatable: { + datatable_enabled: true, + datatable_opened: true, + datatable_selectable_rows: true + } + } + } + ] + ); + + const selectedDatasets: string[] = ['myDataset']; + + const action = searchMultipleActions.initSearch(); + actions = hot('-a', { a: action }); + const expected = cold('-(bc)', { + b: searchMultipleActions.markAsDirty(), + c: searchMultipleActions.updateSelectedDatasets({ selectedDatasets }) + }); + + expect(effects.initSearch$).toBeObservable(expected); + }); + + // it('should dispatch a bunch of actions when a dataset is selected or a page is reloaded with steps checked', () => { + // mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + // datasetSelector.selectDatasetNameByRoute, 'myDatasetName' + // ); + // mockSearchSelectorSelectPristine = store.overrideSelector( + // searchSelector.selectPristine,true + // ); + // mockSearchSelectorSelectCurrentDataset = store.overrideSelector( + // searchSelector.selectCurrentDataset,'myDatasetName' + // ); + // mockSearchSelectorSelectStepsByRoute = store.overrideSelector( + // searchSelector.selectStepsByRoute, '111' + // ); + // + // const action = searchActions.initSearch(); + // actions = hot('-a', { a: action }); + // const expected = cold('-(bcdefghi)', { + // b: searchActions.changeCurrentDataset({ currentDataset: 'myDatasetName' }), + // c: attributeActions.loadAttributeList(), + // d: criteriaFamilyActions.loadCriteriaFamilyList(), + // e: outputFamilyActions.loadOutputFamilyList(), + // f: outputCategoryActions.loadOutputCategoryList(), + // g: searchActions.checkCriteria(), + // h: searchActions.checkOutput(), + // i: searchActions.checkResult() + // }); + // + // expect(effects.initSearch$).toBeObservable(expected); + // }); + // + // it('should dispatch a resetSearch action when user get back to search module', () => { + // mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + // datasetSelector.selectDatasetNameByRoute, '' + // ); + // mockSearchSelectorSelectPristine = store.overrideSelector( + // searchSelector.selectPristine,false + // ); + // + // const action = searchActions.initSearch(); + // const outcome = searchActions.resetSearch(); + // + // actions = hot('-a', { a: action }); + // const expected = cold('-b', { b: outcome }); + // + // expect(effects.initSearch$).toBeObservable(expected); + // }); + // + // it('should not dispatch action when step changed on same search', () => { + // mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + // datasetSelector.selectDatasetNameByRoute, '' + // ); + // + // const action = searchActions.initSearch(); + // const outcome = { type: '[No Action] Init Search' }; + // + // actions = hot('-a', { a: action }); + // const expected = cold('-b', { b: outcome }); + // + // expect(effects.initSearch$).toBeObservable(expected); + // }); + }); + + describe('restartSearch$ effect', () => { + it('should dispatch the initSearch action', () => { + const action = searchMultipleActions.restartSearch(); + const outcome = searchMultipleActions.initSearch(); + + actions = hot('-a', { a: action }); + const expected = cold('-b', { b: outcome }); + + expect(effects.restartSearch$).toBeObservable(expected); + }); + }); + + describe('retrieveDataLength$ effect', () => { + it('should dispatch the retrieveDataLengthSuccess action on success', () => { + mockSearchMultipleSelectorSelectSelectedDatasets = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasets, ['myDataset'] + ); + mockConeSearchSelectorSelectConeSearch = store.overrideSelector( + coneSearchSelector.selectConeSearch, { ra: 1, dec: 2, radius: 3 } + ); + + const dataLength: SearchMultipleDatasetLength[] = [{ datasetName: 'myDataset', length: 1 }]; + const action = searchMultipleActions.retrieveDataLength(); + const outcome = searchMultipleActions.retrieveDataLengthSuccess({ dataLength }); + + actions = hot('-a', { a: action }); + const response = cold('-b|', { b: [{ nb: 1 }] }); + const expected = cold('---c', { c: outcome }); + searchService.retrieveDataLength = jest.fn(() => response); + + expect(effects.retrieveDataLength$).toBeObservable(expected); + }); + + it('should dispatch the retrieveDataLengthFail action on failure', () => { + mockSearchMultipleSelectorSelectSelectedDatasets = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasets, ['myDataset'] + ); + mockConeSearchSelectorSelectConeSearch = store.overrideSelector( + coneSearchSelector.selectConeSearch, { ra: 1, dec: 2, radius: 3 } + ); + + const action = searchMultipleActions.retrieveDataLength(); + const error = new Error(); + const outcome = searchMultipleActions.retrieveDataLengthFail(); + + actions = hot('-a', { a: action }); + const response = cold('-#|', {}, error); + const expected = cold('--b', { b: outcome }); + searchService.retrieveDataLength = jest.fn(() => response); + + expect(effects.retrieveDataLength$).toBeObservable(expected); + }); + + it('should pass correct query to the service', () => { + mockSearchMultipleSelectorSelectSelectedDatasets = store.overrideSelector( + searchMultipleSelector.selectSelectedDatasets, ['myDataset', 'myOtherDataset'] + ); + mockConeSearchSelectorSelectConeSearch = store.overrideSelector( + coneSearchSelector.selectConeSearch, { ra: 1, dec: 2, radius: 3 } + ); + + const dataLength: SearchMultipleDatasetLength[] = [ + { datasetName: 'myDataset', length: 1 }, + { datasetName: 'myOtherDataset', length: 1 } + ]; + + jest.spyOn(searchService, 'retrieveDataLength'); + + const action = searchMultipleActions.retrieveDataLength(); + const outcome = searchMultipleActions.retrieveDataLengthSuccess({ dataLength }); + + actions = hot('-a', { a: action }); + const response = cold('-b|', { b: [{ nb: 1 }] }); + const expected = cold('---c', { c: outcome }); + searchService.retrieveDataLength = jest.fn(() => response); + + expect(effects.retrieveDataLength$).toBeObservable(expected); + expect(searchService.retrieveDataLength).toHaveBeenCalledTimes(2); + expect(searchService.retrieveDataLength).toHaveBeenCalledWith('myDataset?a=count&cs=1:2:3'); + expect(searchService.retrieveDataLength).toHaveBeenCalledWith('myOtherDataset?a=count&cs=1:2:3'); + }); + }); + + describe('retrieveDataLengthFail$ effect', () => { + it('should not dispatch', () => { + expect(metadata.retrieveDataLengthFail$).toEqual( + expect.objectContaining({ dispatch: false }) + ); + }); + + it('should display a error notification', () => { + const spy = jest.spyOn(toastr, 'error'); + const action = searchMultipleActions.retrieveDataLengthFail(); + + actions = hot('a', { a: action }); + const expected = cold('a', { a: action }); + + expect(effects.retrieveDataLengthFail$).toBeObservable(expected); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('Loading Failed', 'The search multiple data length loading failed'); + }); + }); + + // describe('retrieveData$ effect', () => { + // it('should dispatch the retrieveDataSuccess action on success', () => { + // const action = searchActions.retrieveData( { + // pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a } + // }); + // const outcome = searchActions.retrieveDataSuccess({ data: ['data'] }); + // + // actions = hot('-a', { a: action }); + // const response = cold('-b|', { b: ['data'] }); + // const expected = cold('--c', { c: outcome }); + // searchService.retrieveData = jest.fn(() => response); + // + // expect(effects.retrieveData$).toBeObservable(expected); + // }); + // + // it('should dispatch the retrieveDataFail action on failure', () => { + // const action = searchActions.retrieveData({ + // pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a } + // }); + // const error = new Error(); + // const outcome = searchActions.retrieveDataFail(); + // + // actions = hot('-a', { a: action }); + // const response = cold('-#|', {}, error); + // const expected = cold('--b', { b: outcome }); + // searchService.retrieveData = jest.fn(() => response); + // + // expect(effects.retrieveData$).toBeObservable(expected); + // }); + // + // it('should pass correct query to the service', () => { + // mockDatasetSelectorSelectDatasetNameByRoute = store.overrideSelector( + // datasetSelector.selectDatasetNameByRoute, 'myDataset' + // ); + // mockSearchSelectorSelectCriteriaList = store.overrideSelector( + // searchSelector.selectCriteriaList, [{'id':1,'type':'field','operator':'eq','value':'one'} as Criterion] + // ); + // mockConeSearchSelectorSelectConeSearch = store.overrideSelector( + // coneSearchSelector.selectConeSearch, { ra: 1, dec: 2, radius: 3 } + // ); + // mockSearchSelectorSelectOutputList = store.overrideSelector( + // searchSelector.selectOutputList, [1, 2] + // ); + // + // jest.spyOn(searchService, 'retrieveData'); + // + // const action = searchActions.retrieveData({ + // pagination: { dname: 'myDatasetName', page: 1, nbItems: 10, sortedCol: 1, order: PaginationOrder.a } + // }); + // const outcome = searchActions.retrieveDataSuccess({ data: ['data'] }); + // + // actions = hot('-a', { a: action }); + // const response = cold('-b|', { b: ['data'] }); + // const expected = cold('--c', { c: outcome }); + // searchService.retrieveData = jest.fn(() => response); + // + // expect(effects.retrieveData$).toBeObservable(expected); + // expect(searchService.retrieveData).toHaveBeenCalledTimes(1); + // expect(searchService.retrieveData).toHaveBeenCalledWith('myDataset?a=1;2&c=1::eq::one&cs=1:2:3&p=10:1&o=1:a'); + // }); + // }); + + // describe('retrieveDataFail$ effect', () => { + // it('should not dispatch', () => { + // expect(metadata.retrieveDataFail$).toEqual( + // expect.objectContaining({ dispatch: false }) + // ); + // }); + // + // it('should display a error notification', () => { + // const spy = jest.spyOn(toastr, 'error'); + // const action = searchActions.retrieveDataFail(); + // + // actions = hot('a', { a: action }); + // const expected = cold('a', { a: action }); + // + // expect(effects.retrieveDataFail$).toBeObservable(expected); + // expect(spy).toHaveBeenCalledTimes(1); + // expect(spy).toHaveBeenCalledWith('Loading Failed', 'The search data loading failed'); + // }); + // }); +}); diff --git a/client/src/app/instance/store/effects/search-multiple.effects.ts b/client/src/app/instance/store/effects/search-multiple.effects.ts index 84382d44732bfb1c719f7d0cbaa96e4326c693a3..2cd1eeab2499f9ad5e1ca91ba7d6dfda8768c8f1 100644 --- a/client/src/app/instance/store/effects/search-multiple.effects.ts +++ b/client/src/app/instance/store/effects/search-multiple.effects.ts @@ -26,7 +26,11 @@ import * as coneSearchSelector from '../selectors/cone-search.selector'; @Injectable() export class SearchMultipleEffects { - initSearch$ = createEffect(() => + + /** + * Calls actions to initialize search multiple. + */ + initSearch$ = createEffect((): any => this.actions$.pipe( ofType(searchMultipleActions.initSearch), concatLatestFrom(() => [ @@ -37,16 +41,16 @@ export class SearchMultipleEffects { this.store.select(datasetSelector.selectAllConeSearchDatasets) ]), mergeMap(([action, pristine, coneSearchByRoute, selectedDatasetsByRoute, instance, datasetList]) => { + // Restart search if (!pristine && !coneSearchByRoute) { - // Restart search return [ coneSearchActions.deleteConeSearch(), searchMultipleActions.restartSearch() ]; } + // Default form parameters already loaded or no dataset selected if (!pristine) { - // Default form parameters already loaded or no dataset selected return of({ type: '[No Action] Load Default Form Parameters' }); } @@ -86,14 +90,20 @@ export class SearchMultipleEffects { ) ); - restartSearch$ = createEffect(() => + /** + * Calls actions to restart search multiple. + */ + restartSearch$ = createEffect((): any => this.actions$.pipe( ofType(searchMultipleActions.restartSearch), map(() => searchMultipleActions.initSearch()) ) ); - retrieveDataLength$ = createEffect(() => + /** + * Calls actions to retrieve data length. + */ + retrieveDataLength$ = createEffect((): any => this.actions$.pipe( ofType(searchMultipleActions.retrieveDataLength), concatLatestFrom(() => [ @@ -116,11 +126,14 @@ export class SearchMultipleEffects { ) ); - retrieveDataLengthFail$ = createEffect(() => + /** + * Displays retrieve data length error notification. + */ + retrieveDataLengthFail$ = createEffect(() => this.actions$.pipe( ofType(searchMultipleActions.retrieveDataLengthFail), tap(() => this.toastr.error('Loading Failed', 'The search multiple data length loading failed')) - ), { dispatch: false} + ), { dispatch: false } ); constructor( diff --git a/client/src/app/instance/store/models/criterion.model.ts b/client/src/app/instance/store/models/criterion.model.ts index a6118e881162183a5c5bcbf1a4b63b5ad72ec2e9..95f9d974ce488501c9af3e0b868c91b3be1f2ac8 100644 --- a/client/src/app/instance/store/models/criterion.model.ts +++ b/client/src/app/instance/store/models/criterion.model.ts @@ -17,8 +17,9 @@ import { import { Attribute } from 'src/app/metamodel/models'; /** - * @class - * @classdesc Criterion class. + * Interface for criterion. + * + * @interface Criterion */ export interface Criterion { id: number; diff --git a/client/src/app/instance/store/models/criterion/select-multiple-criterion.model.ts b/client/src/app/instance/store/models/criterion/select-multiple-criterion.model.ts index 90fc0ae7499ea8d2f68ba3571503474e74a18532..e1da198fb35d9069df0126c9d1abb91439a8fae9 100644 --- a/client/src/app/instance/store/models/criterion/select-multiple-criterion.model.ts +++ b/client/src/app/instance/store/models/criterion/select-multiple-criterion.model.ts @@ -11,10 +11,9 @@ import { Criterion } from '../criterion.model'; import { Option } from 'src/app/metamodel/models/option.model'; /** - * @class - * @classdesc Select multiple criterion class. + * Interface for select multiple criterion. * - * @implements Criterion + * @interface SelectMultipleCriterion */ export interface SelectMultipleCriterion extends Criterion { id: number; diff --git a/client/src/app/instance/store/models/search-multiple-dataset-data.ts b/client/src/app/instance/store/models/search-multiple-dataset-data.ts index fa25ed88176093d7840cdd984c476aed52b25ed3..0f3d0d86928cb33b5ca735f14aea4fc281b1d4da 100644 --- a/client/src/app/instance/store/models/search-multiple-dataset-data.ts +++ b/client/src/app/instance/store/models/search-multiple-dataset-data.ts @@ -7,6 +7,11 @@ * file that was distributed with this source code. */ +/** + * Interface for search multiple data. + * + * @interface SearchMultipleDatasetData + */ export interface SearchMultipleDatasetData { datasetName: string; data: any[]; diff --git a/client/src/app/instance/store/models/search-multiple-dataset-length.ts b/client/src/app/instance/store/models/search-multiple-dataset-length.ts index 75961a0d5c68acd8878099d32715c9a4c9143242..73a3e0de377302eeefc85bbd15f42be7a7882c46 100644 --- a/client/src/app/instance/store/models/search-multiple-dataset-length.ts +++ b/client/src/app/instance/store/models/search-multiple-dataset-length.ts @@ -7,6 +7,11 @@ * file that was distributed with this source code. */ +/** + * Interface for search multiple data length. + * + * @interface SearchMultipleDatasetLength + */ export interface SearchMultipleDatasetLength { datasetName: string; length: number; diff --git a/client/src/app/instance/store/reducers/search-multiple.reducer.spec.ts b/client/src/app/instance/store/reducers/search-multiple.reducer.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e3ce85eec3702448d7061a4d49d6d11bc0254a9 --- /dev/null +++ b/client/src/app/instance/store/reducers/search-multiple.reducer.spec.ts @@ -0,0 +1,363 @@ +import { Action } from '@ngrx/store'; + +import * as fromSearchMultiple from './search-multiple.reducer'; +import * as searchMultipleActions from '../actions/search-multiple.actions'; +import { SearchMultipleDatasetData, SearchMultipleDatasetLength } from '../models'; + +describe('SearchMultiple reducer', () => { + it('unknown action should return the default state', () => { + const { initialState } = fromSearchMultiple; + const action = { type: 'Unknown' }; + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state).toBe(initialState); + }); + + it('restartSearch action should set currentStep to \'position\'', () => { + const { initialState } = fromSearchMultiple; + const action = searchMultipleActions.restartSearch(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toEqual('position'); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('changeStep action should change the currentStep', () => { + const { initialState } = fromSearchMultiple; + const action = searchMultipleActions.changeStep({ step: 'myStep' }); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toEqual('myStep'); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('markAsDirty action should set pristine to false', () => { + const { initialState } = fromSearchMultiple; + const action = searchMultipleActions.markAsDirty(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeFalsy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('checkDatasets action should set datasetsStepChecked to true', () => { + const { initialState } = fromSearchMultiple; + const action = searchMultipleActions.checkDatasets(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeTruthy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('checkResult action should set resultStepChecked to true', () => { + const { initialState } = fromSearchMultiple; + const action = searchMultipleActions.checkResult(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeTruthy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('updateSelectedDatasets action should set selectedDatasets', () => { + const { initialState } = fromSearchMultiple; + const selectedDatasets: string[] = ['myDataset']; + const action = searchMultipleActions.updateSelectedDatasets({ selectedDatasets }); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(1); + expect(state.selectedDatasets).toEqual(selectedDatasets); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('retrieveDataLength action should set dataLengthIsLoading to true and dataLengthIsLoaded to false', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataLengthIsLoaded: true + }; + const action = searchMultipleActions.retrieveDataLength(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeTruthy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('retrieveDataLengthSuccess action should set dataLength, set dataLengthIsLoading to true and dataLengthIsLoaded to false', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataLengthIsLoading: true + }; + const dataLength: SearchMultipleDatasetLength[] = [{ datasetName: 'myDataset', length: 1 }]; + const action = searchMultipleActions.retrieveDataLengthSuccess({ dataLength }); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeTruthy(); + expect(state.dataLength.length).toEqual(1); + expect(state.dataLength).toEqual(dataLength); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('retrieveDataLengthFail action should set dataLengthIsLoading to false', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataLengthIsLoading: true + }; + const action = searchMultipleActions.retrieveDataLengthFail(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('retrieveData action should set dataIsLoading to true and dataIsLoaded to false', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataIsLoaded: true + }; + const action = searchMultipleActions.retrieveData(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeTruthy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('retrieveDataSuccess action should set data, set dataIsLoading to false and dataIsLoaded to true', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataIsLoading: true + }; + const data: SearchMultipleDatasetData[] = [{ datasetName: 'myDataset', data: ['myData'] }]; + const action = searchMultipleActions.retrieveDataSuccess({ data }); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeTruthy(); + expect(state.data.length).toEqual(1); + expect(state.data).toEqual(data); + expect(state).not.toBe(initialState); + }); + + it('retrieveDataFail action should set dataIsLoading to false', () => { + const initialState = { + ...fromSearchMultiple.initialState, + dataIsLoading: true + }; + const action = searchMultipleActions.retrieveDataFail(); + const state = fromSearchMultiple.searchMultipleReducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull(); + expect(state.positionStepChecked).toBeFalsy(); + expect(state.datasetsStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.selectedDatasets.length).toEqual(0); + expect(state.dataLengthIsLoading).toBeFalsy(); + expect(state.dataLengthIsLoaded).toBeFalsy(); + expect(state.dataLength.length).toEqual(0); + expect(state.dataIsLoading).toBeFalsy(); + expect(state.dataIsLoaded).toBeFalsy(); + expect(state.data.length).toEqual(0); + expect(state).not.toBe(initialState); + }); + + it('should get pristine', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectPristine(state)).toBeTruthy(); + }); + + it('should get currentStep', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectCurrentStep(state)).toBeNull(); + }); + + it('should get positionStepChecked', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectPositionStepChecked(state)).toBeFalsy(); + }); + + it('should get datasetsStepChecked', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDatasetsStepChecked(state)).toBeFalsy(); + }); + + it('should get resultStepChecked', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectResultStepChecked(state)).toBeFalsy(); + }); + + it('should get selectedDatasets', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectSelectedDatasets(state).length).toEqual(0); + }); + + it('should get dataLengthIsLoading', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDataLengthIsLoading(state)).toBeFalsy(); + }); + + it('should get dataLengthIsLoaded', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDataLengthIsLoaded(state)).toBeFalsy(); + }); + + it('should get dataLength', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDataLength(state).length).toEqual(0); + }); + + it('should get dataIsLoading', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDataIsLoading(state)).toBeFalsy(); + }); + + it('should get dataIsLoaded', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectDataIsLoaded(state)).toBeFalsy(); + }); + + it('should get data', () => { + const action = {} as Action; + const state = fromSearchMultiple.searchMultipleReducer(undefined, action); + + expect(fromSearchMultiple.selectData(state).length).toEqual(0); + }); +}); diff --git a/client/src/app/instance/store/reducers/search-multiple.reducer.ts b/client/src/app/instance/store/reducers/search-multiple.reducer.ts index bd73fbba1df15c2853d55d21157e8a7f30fb1dbb..d5cf3af087b2055e2b0caa99b846102e2c363ba2 100644 --- a/client/src/app/instance/store/reducers/search-multiple.reducer.ts +++ b/client/src/app/instance/store/reducers/search-multiple.reducer.ts @@ -12,9 +12,15 @@ import { createReducer, on } from '@ngrx/store'; import { SearchMultipleDatasetLength, SearchMultipleDatasetData } from '../models'; import * as searchMultipleActions from '../actions/search-multiple.actions'; +/** + * Interface for search multiple state. + * + * @interface State + */ export interface State { pristine: boolean; currentStep: string; + // TODO: remove unused positionStepChecked variable positionStepChecked: boolean; datasetsStepChecked: boolean; resultStepChecked: boolean; diff --git a/client/src/app/instance/store/selectors/search-multiple.selector.spec.ts b/client/src/app/instance/store/selectors/search-multiple.selector.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b21c4ac104900ee2b3b3beef6ad151141cf8007 --- /dev/null +++ b/client/src/app/instance/store/selectors/search-multiple.selector.spec.ts @@ -0,0 +1,115 @@ +import * as searchMultipleSelector from './search-multiple.selector'; +import * as fromSearchMultiple from '../reducers/search-multiple.reducer'; +import * as fromConeSearch from '../reducers/cone-search.reducer'; +import { ConeSearch } from '../models'; + +describe('Search multiple selector', () => { + it('should get selectPristine', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectPristine(state)).toBeTruthy(); + }); + + it('should get currentStep', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectCurrentStep(state)).toBeNull(); + }); + + it('should get positionStepChecked', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectPositionStepChecked(state)).toBeFalsy(); + }); + + it('should get datasetsStepChecked', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDatasetsStepChecked(state)).toBeFalsy(); + }); + + it('should get resultStepChecked', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectResultStepChecked(state)).toBeFalsy(); + }); + + it('should get selectedDatasets', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectSelectedDatasets(state).length).toEqual(0); + }); + + it('should get dataLengthIsLoading', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDataLengthIsLoading(state)).toBeFalsy(); + }); + + it('should get dataLengthIsLoaded', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDataLengthIsLoaded(state)).toBeFalsy(); + }); + + it('should get dataLength', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDataLength(state).length).toEqual(0); + }); + + it('should get dataIsLoading', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDataIsLoading(state)).toBeFalsy(); + }); + + it('should get dataIsLoaded', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectDataIsLoaded(state)).toBeFalsy(); + }); + + it('should get data', () => { + const state = { instance: { searchMultiple: { ...fromSearchMultiple.initialState }}}; + expect(searchMultipleSelector.selectData(state).length).toEqual(0); + }); + + it('should get queryParams', () => { + const state = { + instance: { + searchMultiple: { ...fromSearchMultiple.initialState }, + coneSearch: { ...fromConeSearch.initialState } + } + }; + const expected = { }; + + expect(searchMultipleSelector.selectQueryParams(state)).toEqual(expected); + }); + + it('should get queryParams with cone search', () => { + const coneSearch: ConeSearch = { ra: 3, dec: 4, radius: 5 }; + const state = { + instance: { + searchMultiple: { ...fromSearchMultiple.initialState }, + coneSearch: { + ...fromConeSearch.initialState, + coneSearch + } + } + }; + const expected = { cs: '3:4:5' }; + + expect(searchMultipleSelector.selectQueryParams(state)).toEqual(expected); + }); + + it('should get queryParams with datasets', () => { + const selectedDatasets: string[] = ['d1', 'd2']; + const state = { + instance: { + searchMultiple: { + ...fromSearchMultiple.initialState, + selectedDatasets + }, + coneSearch: { ...fromConeSearch.initialState } + } + }; + const expected = { d: 'd1;d2' }; + + expect(searchMultipleSelector.selectQueryParams(state)).toEqual(expected); + }); + + it('should get steps by route', () => { + const state = { router: { state: { queryParams: { d: 'd1;d2' }}}}; + expect(searchMultipleSelector.selectSelectedDatasetsByRoute(state)).toEqual('d1;d2'); + }); +});