diff --git a/src/app/components/search/search-input/search-input.component.ts b/src/app/components/search/search-input/search-input.component.ts index e2c4f4a70..53c07bf68 100644 --- a/src/app/components/search/search-input/search-input.component.ts +++ b/src/app/components/search/search-input/search-input.component.ts @@ -131,7 +131,9 @@ export class SearchInputComponent implements OnInit, OnDestroy { if (urlSegmentGroup) { const urlSegments: UrlSegment[] = urlSegmentGroup.segments; - this.searchedWord = urlSegments[0].parameters['q'] || ''; + this.searchedWord = urlSegments[0].parameters['q'] + ? decodeURIComponent(urlSegments[0].parameters['q']) + : ''; } } diff --git a/src/app/components/search/search-results/search-results.component.spec.ts b/src/app/components/search/search-results/search-results.component.spec.ts index 754005081..6481c785b 100644 --- a/src/app/components/search/search-results/search-results.component.spec.ts +++ b/src/app/components/search/search-results/search-results.component.spec.ts @@ -8,6 +8,7 @@ import { Store } from '@ngrx/store'; import { NavigateToFolder } from '../../../store/actions'; import { Pagination } from '@alfresco/js-api'; import { SearchQueryBuilderService } from '@alfresco/adf-content-services'; +import { ActivatedRoute } from '@angular/router'; describe('SearchComponent', () => { let component: SearchResultsComponent; @@ -18,7 +19,25 @@ describe('SearchComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [CoreModule.forRoot(), AppTestingModule, AppSearchResultsModule] + imports: [CoreModule.forRoot(), AppTestingModule, AppSearchResultsModule], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + data: { + sortingPreferenceKey: '' + } + }, + params: [ + { + q: + 'TYPE: "cm:folder" AND %28=cm: name: email OR cm: name: budget%29' + } + ] + } + } + ] }); config = TestBed.get(AppConfigService); @@ -27,9 +46,18 @@ describe('SearchComponent', () => { fixture = TestBed.createComponent(SearchResultsComponent); component = fixture.componentInstance; + + spyOn(queryBuilder, 'update').and.stub(); + fixture.detectChanges(); })); + it('should decode encoded URI', () => { + expect(queryBuilder.userQuery).toEqual( + '(TYPE: "cm:folder" AND (=cm: name: email OR cm: name: budget))' + ); + }); + it('should return null if formatting invalid query', () => { expect(component.formatSearchQuery(null)).toBeNull(); expect(component.formatSearchQuery('')).toBeNull(); @@ -169,8 +197,6 @@ describe('SearchComponent', () => { skipCount: 0 }); - spyOn(queryBuilder, 'update').and.stub(); - component.onPaginationChanged(page); expect(queryBuilder.paging).toEqual({ diff --git a/src/app/components/search/search-results/search-results.component.ts b/src/app/components/search/search-results/search-results.component.ts index 6b4d552a7..f183f61f5 100644 --- a/src/app/components/search/search-results/search-results.component.ts +++ b/src/app/components/search/search-results/search-results.component.ts @@ -102,7 +102,7 @@ export class SearchResultsComponent extends PageComponent implements OnInit { const query = this.formatSearchQuery(this.searchedWord); if (query) { - this.queryBuilder.userQuery = query; + this.queryBuilder.userQuery = decodeURIComponent(query); this.queryBuilder.update(); } else { this.queryBuilder.userQuery = null; diff --git a/src/app/store/effects/search.effects.spec.ts b/src/app/store/effects/search.effects.spec.ts new file mode 100644 index 000000000..639a316a8 --- /dev/null +++ b/src/app/store/effects/search.effects.spec.ts @@ -0,0 +1,78 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { AppTestingModule } from '../../testing/app-testing.module'; +import { SearchEffects } from './search.effects'; +import { EffectsModule } from '@ngrx/effects'; +import { Store } from '@ngrx/store'; +import { SearchByTermAction } from '../actions/search.actions'; +import { Router } from '@angular/router'; + +describe('SearchEffects', () => { + let store: Store; + let router: Router; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [AppTestingModule, EffectsModule.forRoot([SearchEffects])] + }); + + store = TestBed.get(Store); + router = TestBed.get(Router); + + spyOn(router, 'navigateByUrl').and.stub(); + }); + + describe('searchByTerm$', () => { + it('should navigate to `search` when search options has library false', fakeAsync(() => { + store.dispatch(new SearchByTermAction('test', [])); + tick(); + expect(router.navigateByUrl).toHaveBeenCalledWith('/search;q=test'); + })); + + it('should navigate to `search-libraries` when search options has library true', fakeAsync(() => { + store.dispatch( + new SearchByTermAction('test', [{ id: 'libraries', value: true }]) + ); + + tick(); + + expect(router.navigateByUrl).toHaveBeenCalledWith( + '/search-libraries;q=test' + ); + })); + + it('should encode search string for parentheses', fakeAsync(() => { + store.dispatch(new SearchByTermAction('(test)', [])); + + tick(); + + expect(router.navigateByUrl).toHaveBeenCalledWith( + '/search;q=%2528test%2529' + ); + })); + }); +}); diff --git a/src/app/store/effects/search.effects.ts b/src/app/store/effects/search.effects.ts index 2f5cc3673..8b9063c4e 100644 --- a/src/app/store/effects/search.effects.ts +++ b/src/app/store/effects/search.effects.ts @@ -38,18 +38,20 @@ export class SearchEffects { searchByTerm$ = this.actions$.pipe( ofType(SEARCH_BY_TERM), map(action => { + const query = action.payload + .replace(/[(]/g, '%28') + .replace(/[)]/g, '%29'); + const libItem = action.searchOptions.find( item => item.id === SearchOptionIds.Libraries ); const librarySelected = !!libItem && libItem.value; if (librarySelected) { this.router.navigateByUrl( - '/search-libraries;q=' + encodeURIComponent(action.payload) + '/search-libraries;q=' + encodeURIComponent(query) ); } else { - this.router.navigateByUrl( - '/search;q=' + encodeURIComponent(action.payload) - ); + this.router.navigateByUrl('/search;q=' + encodeURIComponent(query)); } }) );