mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[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:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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">
|
||||
|
@@ -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);
|
||||
});
|
||||
}));
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
|
@@ -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');
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@@ -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();
|
||||
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;
|
||||
|
Reference in New Issue
Block a user