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 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 coneSearchSelector from '../selectors/cone-search.selector'; import * as coneSearchActions from '../actions/cone-search.actions'; import { ConeSearch, 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('[Instance][Store] 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', description: 'My Instance description', scientific_manager: 'M. Dupont', instrument: 'Multiple', wavelength_domain: 'Visible', display: 10, data_path: 'data/path', files_path: 'files', public: true, portal_logo: 'logo.png', design_color: 'green', design_background_color: 'darker green', design_logo: 'path/to/logo', design_favicon: 'path/to/favicon', navbar_background_color: '#F8F9FA', navbar_border_bottom_color: '#DEE2E6', navbar_color_href: '#000000', navbar_font_family: 'Roboto, sans-serif', navbar_sign_in_btn_color: '#28A745', navbar_user_btn_color: '#7AC29A', footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', family_title_bold: false, family_background_color: '#FFFFFF', family_color: '#212529', footer_logos: null, progress_bar_title: 'Dataset search', progress_bar_title_color: '#000000', progress_bar_subtitle: 'Select a dataset, add criteria, select output columns and display the result.', progress_bar_subtitle_color: '#6C757D', progress_bar_step_dataset_title: 'Dataset selection', progress_bar_step_criteria_title: 'Search criteria', progress_bar_step_output_title: 'Output columns', progress_bar_step_result_title: 'Result table', progress_bar_color: '#E9ECEF', progress_bar_active_color: '#7AC29A', progress_bar_circle_color: '#FFFFFF', progress_bar_circle_icon_color: '#CCCCCC', progress_bar_circle_icon_active_color: '#FFFFFF', progress_bar_text_color: '#91B2BF', result_header_background_color: '#E9ECEF', result_header_text_color: '#000000', result_header_btn_color: '#007BFF', result_header_btn_hover_color: '#0069D9', result_header_btn_text_color: '#FFFFFF', result_datatable_bordered: true, result_datatable_border_color: '#DEE2E6', result_datatable_header_background_color: '#FFFFFF', result_datatable_header_text_color: '#000000', result_datatable_rows_background_color: '#FFFFFF', result_datatable_rows_text_color: '#000000', result_datatable_sorted_color: '#C5C5C5', result_datatable_sorted_active_color: '#000000', result_datatable_link_color: '#007BFF', result_datatable_link_hover_color: '#0056B3', result_datatable_rows_selected_color: '#7AC29A', samp_enabled: true, back_to_portal: true, user_menu_enabled: true, 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_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', description: 'My Instance description', scientific_manager: 'M. Dupont', instrument: 'Multiple', wavelength_domain: 'Visible', display: 10, data_path: 'data/path', files_path: 'files', public: true, portal_logo: 'logo.png', design_color: 'green', design_background_color: 'darker green', design_logo: 'path/to/logo', design_favicon: 'path/to/favicon', navbar_background_color: '#F8F9FA', navbar_border_bottom_color: '#DEE2E6', navbar_color_href: '#000000', navbar_font_family: 'Roboto, sans-serif', navbar_sign_in_btn_color: '#28A745', navbar_user_btn_color: '#7AC29A', footer_background_color: '#F8F9FA', footer_border_top_color: '#DEE2E6', footer_text_color: '#000000', footer_logos: null, family_border_color: '#DFDFDF', family_header_background_color: '#F7F7F7', family_title_color: '#007BFF', family_title_bold: false, family_background_color: '#FFFFFF', family_color: '#212529', progress_bar_title: 'Dataset search', progress_bar_title_color: '#000000', progress_bar_subtitle: 'Select a dataset, add criteria, select output columns and display the result.', progress_bar_subtitle_color: '#6C757D', progress_bar_step_dataset_title: 'Dataset selection', progress_bar_step_criteria_title: 'Search criteria', progress_bar_step_output_title: 'Output columns', progress_bar_step_result_title: 'Result table', progress_bar_color: '#E9ECEF', progress_bar_active_color: '#7AC29A', progress_bar_circle_color: '#FFFFFF', progress_bar_circle_icon_color: '#CCCCCC', progress_bar_circle_icon_active_color: '#FFFFFF', progress_bar_text_color: '#91B2BF', result_header_background_color: '#E9ECEF', result_header_text_color: '#000000', result_header_btn_color: '#007BFF', result_header_btn_hover_color: '#0069D9', result_header_btn_text_color: '#FFFFFF', result_datatable_bordered: true, result_datatable_border_color: '#DEE2E6', result_datatable_header_background_color: '#FFFFFF', result_datatable_header_text_color: '#000000', result_datatable_rows_background_color: '#FFFFFF', result_datatable_rows_text_color: '#000000', result_datatable_sorted_color: '#C5C5C5', result_datatable_sorted_active_color: '#000000', result_datatable_link_color: '#007BFF', result_datatable_link_hover_color: '#0056B3', result_datatable_rows_selected_color: '#7AC29A', samp_enabled: true, back_to_portal: true, user_menu_enabled: true, 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_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: '', public: true, download_json: true, download_csv: true, download_ascii: true, download_vo: true, server_link_enabled: true, datatable_enabled: true, datatable_selectable_rows: true, cone_search_config_id: 1, id_database: 1, id_dataset_family: 1, full_data_path: '' } ] ); 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); }); }); 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'); }); }); });