[ADF-1918] added new search api service to search component (#2667)

* [ADF-1918] added new search api service to search component

* [ADF-1918] fixed close panel on empty search word

* [ADF-1918] added documentation for search changes

* [ADF-1918] fixed closing of subscription on destroy
This commit is contained in:
Vito
2017-11-19 20:44:12 +00:00
committed by Eugenio Romano
parent a105ab0334
commit 0f0f22634a
9 changed files with 283 additions and 113 deletions

View File

@@ -1,4 +1,4 @@
# Search component
# Search control component
Displays a input text which shows find-as-you-type suggestions.
@@ -38,10 +38,8 @@ Displays a input text which shows find-as-you-type suggestions.
| highlight | boolean | false | Use the true value if you want to see the searched word highlighted. |
| expandable | boolean | true | Whether to use an expanding search control, if false then a regular input is used. |
| liveSearchEnabled | boolean | true | Whether find-as-you-type suggestions should be offered for matching content items. Set to false to disable. |
| liveSearchRoot | string | "-root-" | NodeRef or node name where the search should start. |
| liveSearchResultType | string | | Node type to filter live search results by, e.g. 'cm:content'. |
| liveSearchMaxResults | number | 5 | Maximum number of results to show in the live search. |
| liveSearchResultSort | string | | Criteria to sort live search results by, must be one of "name" , "modifiedAt" or "createdAt" |
| customSearchNode | [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api |
### Events

View File

@@ -1,4 +1,4 @@
# Search Results component
# Search component
@@ -29,11 +29,10 @@
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| searchTerm | string | | Search term to use when executing the search. Updating this value will run a new search and update the results |
| rootNodeId | string | "-root-" | NodeRef or node name where the search should start. |
| resultType | string | | Node type to filter search results by, e.g. 'cm:content', 'cm:folder' if you want only the files. |
| maxResults | number | 20 | Maximum number of results to show in the search. |
| resultSort | string | | Criteria to sort search results by, must be one of "name" , "modifiedAt" or "createdAt" |
| skipResults | number | 0 | Number of results to skip from the results pagination. |
| displayWith | function | | Function that maps an option's value to its display value in the trigger |
| searchNode | [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api |
### Events

View File

@@ -17,6 +17,7 @@
import { Component, ViewChild } from '@angular/core';
import { SearchComponent } from '../search/components/search.component';
import { QueryBody } from 'alfresco-js-api';
const entryItem = {
entry: {
@@ -117,7 +118,7 @@ export let errorJson = {
@Component({
template: `
<adf-search [searchTerm]="searchedWord" [maxResults]="maxResults"
<adf-search [searchTerm]="searchedWord" [searchNode]="searchNode" [maxResults]="maxResults"
(error)="showSearchResult('ERROR')"
(success)="showSearchResult('success')" #search>
<ng-template let-data>
@@ -142,6 +143,7 @@ export let errorJson = {
message: string = '';
searchedWord= '';
maxResults: number = 5;
searchNode: QueryBody;
constructor() {
}
@@ -158,6 +160,10 @@ export let errorJson = {
this.searchedWord = str;
}
setSearchNodeTo(searchNode: QueryBody) {
this.searchNode = searchNode;
}
changeMaxResultTo(newMax: number) {
this.maxResults = newMax;
}

View File

@@ -25,9 +25,7 @@
<adf-search #auto="searchAutocomplete"
class="adf-search-result-autocomplete"
[rootNodeId]="liveSearchRoot"
[resultType]="liveSearchResultType"
[resultSort]="liveSearchResultSort"
[searchNode]="customSearchNode"
[maxResults]="liveSearchMaxResults">
<ng-template let-data>
<mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list">

View File

@@ -19,7 +19,7 @@ import { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MaterialModule } from '../../material.module';
import { By } from '@angular/platform-browser';
import { AuthenticationService, SearchService } from '@alfresco/adf-core';
import { AuthenticationService, SearchApiService } from '@alfresco/adf-core';
import { ThumbnailService } from '@alfresco/adf-core';
import { Observable } from 'rxjs/Observable';
import { noResult, results } from '../../mock';
@@ -27,13 +27,13 @@ import { SearchControlComponent } from './search-control.component';
import { SearchTriggerDirective } from './search-trigger.directive';
import { SearchComponent } from './search.component';
xdescribe('SearchControlComponent', () => {
describe('SearchControlComponent', () => {
let fixture: ComponentFixture<SearchControlComponent>;
let component: SearchControlComponent;
let element: HTMLElement;
let debugElement: DebugElement;
let searchService: SearchService;
let searchService: SearchApiService;
let authService: AuthenticationService;
beforeEach(async(() => {
@@ -48,12 +48,12 @@ xdescribe('SearchControlComponent', () => {
],
providers: [
ThumbnailService,
SearchService
SearchApiService
]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(SearchControlComponent);
debugElement = fixture.debugElement;
searchService = TestBed.get(SearchService);
searchService = TestBed.get(SearchApiService);
authService = TestBed.get(AuthenticationService);
component = fixture.componentInstance;
element = fixture.nativeElement;
@@ -76,14 +76,14 @@ xdescribe('SearchControlComponent', () => {
}));
it('should emit searchChange when search term input changed', async(() => {
spyOn(searchService, 'getNodeQueryResults').and.callFake(() => {
return Observable.of({ entry: { list: []}});
spyOn(searchService, 'search').and.callFake(() => {
return Observable.of({ entry: { list: [] } });
});
component.searchChange.subscribe(value => {
expect(value).toBe('customSearchTerm');
});
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'customSearchTerm';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -91,12 +91,12 @@ xdescribe('SearchControlComponent', () => {
}));
it('should update FAYT search when user inputs a valid term', async(() => {
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'customSearchTerm';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
fixture.whenStable().then(() => {
@@ -107,13 +107,13 @@ xdescribe('SearchControlComponent', () => {
});
}));
it('should NOT update FAYT term when user inputs a search term less than 3 characters', async(() => {
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'cu';
it('should NOT update FAYT term when user inputs an empty string as search term ', async(() => {
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = '';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
fixture.whenStable().then(() => {
@@ -126,7 +126,7 @@ xdescribe('SearchControlComponent', () => {
component.searchChange.subscribe(value => {
expect(value).toBe('cu');
});
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'cu';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -184,10 +184,10 @@ xdescribe('SearchControlComponent', () => {
});
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -206,10 +206,10 @@ xdescribe('SearchControlComponent', () => {
it('should make autocomplete list control visible when search box has focus and there is a search result', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -224,10 +224,10 @@ xdescribe('SearchControlComponent', () => {
it('should show autocomplete list noe results when search box has focus and there is search result with length 0', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(noResult));
spyOn(searchService, 'search').and.returnValue(Observable.of(noResult));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'NO RES';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -241,10 +241,10 @@ xdescribe('SearchControlComponent', () => {
it('should hide autocomplete list results when the search box loses focus', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'NO RES';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -264,10 +264,10 @@ xdescribe('SearchControlComponent', () => {
it('should keep autocomplete list control visible when user tabs into results', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -280,39 +280,89 @@ xdescribe('SearchControlComponent', () => {
inputDebugElement.nativeElement.dispatchEvent(new KeyboardEvent('keypress', { key: 'TAB' }));
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list') ).not.toBeNull();
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
});
}));
it('should close the autocomplete when user press ESCAPE', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
expect(resultElement).not.toBeNull();
let escapeEvent: any = new Event('ESCAPE');
escapeEvent.keyCode = 27;
inputDebugElement.triggerEventHandler('keydown', escapeEvent);
fixture.whenStable().then(() => {
fixture.detectChanges();
resultElement = <HTMLElement> element.querySelector('#result_option_0');
expect(resultElement).toBeNull();
});
});
}));
it('should close the autocomplete when user press ENTER on input', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
expect(resultElement).not.toBeNull();
let escapeEvent: any = new Event('ENTER');
escapeEvent.keyCode = 13;
inputDebugElement.triggerEventHandler('keydown', escapeEvent);
fixture.whenStable().then(() => {
fixture.detectChanges();
resultElement = <HTMLElement> element.querySelector('#result_option_0');
expect(resultElement).toBeNull();
});
});
}));
it('should focus input element when autocomplete list is cancelled', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
let escapeEvent: any = new Event('ESCAPE');
escapeEvent.keyCode = 27;
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(escapeEvent);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('#result_name_0') ).toBeNull();
expect(element.querySelector('#result_name_0')).toBeNull();
expect(document.activeElement.id).toBe(inputDebugElement.nativeElement.id);
});
}));
it('should NOT display a autocomplete list control when configured not to', async(() => {
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
component.liveSearchEnabled = false;
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list') ).toBeNull();
expect(element.querySelector('#autocomplete-search-result-list')).toBeNull();
});
}));
});
@@ -320,41 +370,58 @@ xdescribe('SearchControlComponent', () => {
describe('search button', () => {
it('should NOT display a autocomplete list control when configured not to', async(() => {
fixture.detectChanges();
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
expect(component.subscriptAnimationState).toBe('active');
searchButton.triggerEventHandler('click', null);
fixture.whenStable().then(() => {
window.setTimeout(() => {
fixture.detectChanges();
expect(component.subscriptAnimationState).toBe('inactive');
});
}, 100);
}));
it('click on the search button should open the input box when is close', (done) => {
component.subscriptAnimationState = 'inactive';
fixture.detectChanges();
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
searchButton.triggerEventHandler('click', null);
window.setTimeout(() => {
fixture.detectChanges();
expect(component.subscriptAnimationState).toBe('active');
done();
}, 200);
}, 100);
});
it('Search button should not change the input state too often', async(() => {
fixture.detectChanges();
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
expect(component.subscriptAnimationState).toBe('active');
searchButton.triggerEventHandler('click', null);
fixture.detectChanges();
searchButton.triggerEventHandler('click', null);
fixture.detectChanges();
fixture.whenStable().then(() => {
window.setTimeout(() => {
fixture.detectChanges();
expect(component.subscriptAnimationState).toBe('inactive');
});
}, 100);
}));
it('Search bar should close when user press ESC button', async(() => {
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
expect(component.subscriptAnimationState).toBe('active');
inputDebugElement.triggerEventHandler('keyup.escape', {});
window.setTimeout(() => {
fixture.detectChanges();
expect(component.subscriptAnimationState).toBe('inactive');
}, 100);
}));
});
@@ -362,26 +429,26 @@ xdescribe('SearchControlComponent', () => {
it('should emit a option clicked event when item is clicked', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
expect(item.entry.id).toBe('123');
});
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.triggerEventHandler('click', null);
});
}));
it('should set deactivate the search after element is clicked', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
window.setTimeout(() => {
expect(component.subscriptAnimationState).toBe('inactive');
@@ -389,27 +456,27 @@ xdescribe('SearchControlComponent', () => {
});
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.triggerEventHandler('click', null);
});
}));
it('should NOT reset the search term after element is clicked', async(() => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
spyOn(searchService, 'search').and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
expect(component.searchTerm).not.toBeFalsy();
expect(component.searchTerm).toBe('TEST');
});
fixture.detectChanges();
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'TEST';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
@@ -417,7 +484,7 @@ xdescribe('SearchControlComponent', () => {
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.triggerEventHandler('click', null);
});
}));

View File

@@ -18,7 +18,7 @@
import { AuthenticationService, ThumbnailService } from '@alfresco/adf-core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { MinimalNodeEntity } from 'alfresco-js-api';
import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
@@ -55,18 +55,12 @@ export class SearchControlComponent implements OnInit, OnDestroy {
@Input()
liveSearchEnabled: boolean = true;
@Input()
liveSearchRoot: string = '-root-';
@Input()
liveSearchResultType: string = null;
@Input()
liveSearchResultSort: string = null;
@Input()
liveSearchMaxResults: number = 5;
@Input()
customSearchNode: QueryBody;
@Output()
submit: EventEmitter<any> = new EventEmitter();

View File

@@ -46,8 +46,6 @@ export const SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
multi: true
};
const MIN_WORD_LENGTH_VALID = 3;
@Directive({
selector: `input[searchAutocomplete], textarea[searchAutocomplete]`,
host: {
@@ -82,6 +80,9 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
ngOnDestroy() {
this.escapeEventStream.unsubscribe();
if ( this.closingActionsSubscription ) {
this.closingActionsSubscription.unsubscribe();
}
}
get panelOpen(): boolean {
@@ -160,11 +161,11 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
if (document.activeElement === event.target) {
let inputValue: string = (event.target as HTMLInputElement).value;
this.onChange(inputValue);
if (inputValue.length >= MIN_WORD_LENGTH_VALID) {
if (inputValue) {
this.searchPanel.keyPressedStream.next(inputValue);
this.openPanel();
} else {
this.searchPanel.resetResults();
this.closePanel();
}
}
}
@@ -193,7 +194,6 @@ export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
this.searchPanel.setVisibility();
return this.panelClosingActions;
})
.first()
.subscribe(event => this.setValueAndClose(event));
}

View File

@@ -16,27 +16,40 @@
*/
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchService } from '@alfresco/adf-core';
import { SearchApiService } from '@alfresco/adf-core';
import { QueryBody } from 'alfresco-js-api';
import { Observable } from 'rxjs/Observable';
import { SearchModule } from '../../index';
import { differentResult, result, SimpleSearchTestComponent } from '../../mock';
import { differentResult, folderResult, result, SimpleSearchTestComponent } from '../../mock';
function fakeNodeResultSearch(searchNode: QueryBody): Observable<any> {
if (searchNode.query.query === 'FAKE_SEARCH_EXMPL') {
return Observable.of(differentResult);
}
if (searchNode.filterQueries.length === 1 &&
searchNode.filterQueries[0].query === "TYPE:'cm:folder'") {
return Observable.of(folderResult);
}
return Observable.of(result);
}
describe('SearchComponent', () => {
let fixture: ComponentFixture<SimpleSearchTestComponent>, element: HTMLElement;
let component: SimpleSearchTestComponent;
let searchService: SearchService;
let searchService: SearchApiService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SearchModule
],
declarations: [ SimpleSearchTestComponent ]
declarations: [SimpleSearchTestComponent]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(SimpleSearchTestComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
searchService = TestBed.get(SearchService);
searchService = TestBed.get(SearchApiService);
});
}));
@@ -47,8 +60,8 @@ describe('SearchComponent', () => {
});
it('should clear results straight away when a new search term is entered', async(() => {
spyOn(searchService, 'getQueryNodesPromise')
.and.returnValues(Promise.resolve(result), Promise.resolve(differentResult));
spyOn(searchService, 'search')
.and.returnValues(Observable.of(result), Observable.of(differentResult));
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
@@ -67,8 +80,8 @@ describe('SearchComponent', () => {
}));
it('should display the returned search results', async(() => {
spyOn(searchService, 'getQueryNodesPromise')
.and.returnValue(Promise.resolve(result));
spyOn(searchService, 'search')
.and.returnValue(Observable.of(result));
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
@@ -80,8 +93,8 @@ describe('SearchComponent', () => {
}));
it('should emit error event when search call fail', async(() => {
spyOn(searchService, 'getQueryNodesPromise')
.and.returnValue(Promise.reject({ status: 402 }));
spyOn(searchService, 'search')
.and.returnValue(Observable.fromPromise(Promise.reject({ status: 402 })));
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
fixture.whenStable().then(() => {
@@ -92,8 +105,8 @@ describe('SearchComponent', () => {
}));
it('should be able to hide the result panel', async(() => {
spyOn(searchService, 'getQueryNodesPromise')
.and.returnValues(Promise.resolve(result), Promise.resolve(differentResult));
spyOn(searchService, 'search')
.and.returnValues(Observable.of(result), Observable.of(differentResult));
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
@@ -111,4 +124,70 @@ describe('SearchComponent', () => {
});
}));
});
describe('search node', () => {
afterEach(() => {
fixture.destroy();
});
it('should perform a search based on the query node given', async(() => {
spyOn(searchService, 'search')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj));
let fakeSearchNode: QueryBody = {
query: {
query: ''
},
filterQueries: [
{ 'query': "TYPE:'cm:folder'" }
]
};
component.setSearchNodeTo(fakeSearchNode);
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let optionShowed = element.querySelectorAll('#autocomplete-search-result-list > li').length;
expect(optionShowed).toBe(1);
let folderOption: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
expect(folderOption.textContent.trim()).toBe('MyFolder');
});
}));
it('should perform a search with a defaultNode if no searchnode is given', async(() => {
spyOn(searchService, 'search')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj));
component.setSearchWordTo('searchTerm');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let optionShowed = element.querySelectorAll('#autocomplete-search-result-list > li').length;
expect(optionShowed).toBe(1);
let folderOption: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
expect(folderOption.textContent.trim()).toBe('MyDoc');
});
}));
it('should perform a search with the searchNode given', async(() => {
spyOn(searchService, 'search')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj));
let fakeSearchNode: QueryBody = {
query: {
query: 'FAKE_SEARCH_EXMPL'
},
filterQueries: [
{ 'query': "TYPE:'cm:folder'" }
]
};
component.setSearchNodeTo(fakeSearchNode);
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let optionShowed = element.querySelectorAll('#autocomplete-search-result-list > li').length;
expect(optionShowed).toBe(1);
let folderOption: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
expect(folderOption.textContent.trim()).toBe('TEST_DOC');
});
}));
});
});

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { SearchOptions, SearchService } from '@alfresco/adf-core';
import { SearchApiService } from '@alfresco/adf-core';
import {
AfterContentInit,
ChangeDetectionStrategy,
@@ -31,7 +31,7 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { NodePaging } from 'alfresco-js-api';
import { NodePaging, QueryBody } from 'alfresco-js-api';
import { Subject } from 'rxjs/Subject';
@Component({
@@ -61,17 +61,14 @@ export class SearchComponent implements AfterContentInit, OnChanges {
maxResults: number = 20;
@Input()
resultSort: string = null;
@Input()
rootNodeId: string = '-root-';
@Input()
resultType: string = null;
skipResults: number = 0;
@Input()
searchTerm: string = '';
@Input()
searchNode: QueryBody;
@Input('class')
set classList(classList: string) {
if (classList && classList.length) {
@@ -104,7 +101,7 @@ export class SearchComponent implements AfterContentInit, OnChanges {
_classList: { [key: string]: boolean } = {};
constructor(
private searchService: SearchService,
private searchService: SearchApiService,
private changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef) {
this.keyPressedStream.asObservable()
@@ -119,10 +116,13 @@ export class SearchComponent implements AfterContentInit, OnChanges {
}
ngOnChanges(changes) {
if (changes.searchTerm) {
this.resetResults();
if (changes.searchTerm && changes.searchTerm.currentValue) {
this.displaySearchResults(changes.searchTerm.currentValue);
}
if (changes.searchNode && changes.searchNode.currentValue) {
this.displaySearchResults();
}
}
resetResults() {
@@ -140,18 +140,17 @@ export class SearchComponent implements AfterContentInit, OnChanges {
}
}
private displaySearchResults(searchTerm) {
let searchOpts: SearchOptions = {
include: ['path', 'allowableOperations'],
rootNodeId: this.rootNodeId,
nodeType: this.resultType,
maxItems: this.maxResults,
orderBy: this.resultSort
};
if (searchTerm !== null && searchTerm !== '') {
private hasValidSearchQuery(searchOpts: QueryBody) {
return searchOpts && searchOpts.query && searchOpts.query.query;
}
private displaySearchResults(searchTerm?: string) {
let searchOpts: QueryBody = this.getSearchNode(searchTerm);
if (this.hasValidSearchQuery(searchOpts)) {
searchTerm = searchTerm + '*';
this.searchService
.getNodeQueryResults(searchTerm, searchOpts)
.search(searchOpts)
.subscribe(
results => {
this.results = <NodePaging> results;
@@ -165,9 +164,39 @@ export class SearchComponent implements AfterContentInit, OnChanges {
this.error.emit(error);
}
});
} else {
this.cleanResults();
}
}
private getSearchNode(searchTerm: string): QueryBody {
if (this.searchNode) {
if (!this.searchNode.query.query && searchTerm) {
this.searchNode.query.query = searchTerm;
}
return this.searchNode;
} else {
return this.generateDefaultSearchNode(searchTerm);
}
}
private generateDefaultSearchNode(searchTerm: string): QueryBody {
let defaultSearchNode: QueryBody = {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
paging: {
maxItems: this.maxResults.toString(),
skipCount: this.skipResults.toString()
},
filterQueries: [
{ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ query: 'NOT cm:creator:System' }]
};
return defaultSearchNode;
}
hidePanel() {
if (this.isOpen) {
this._classList['adf-search-show'] = false;