/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, DebugElement, ViewChild } from '@angular/core';
import { async, discardPeriodicTasks, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AuthenticationService, SearchService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { ThumbnailService } from '@alfresco/adf-core';
import { noResult, results } from '../../mock';
import { SearchControlComponent } from './search-control.component';
import { SearchTriggerDirective } from './search-trigger.directive';
import { SearchComponent } from './search.component';
import { EmptySearchResultComponent } from './empty-search-result.component';
import { Observable } from 'rxjs/Observable';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@Component({
template: `
{{customMessage}}
`
})
export class SimpleSearchTestCustomEmptyComponent {
customMessage: string = '';
@ViewChild(SearchControlComponent)
searchComponent: SearchControlComponent;
constructor() {
}
setCustomMessageForNoResult(message: string) {
this.customMessage = message;
}
}
describe('SearchControlComponent', () => {
let fixture: ComponentFixture;
let component: SearchControlComponent;
let element: HTMLElement;
let debugElement: DebugElement;
let searchService: SearchService;
let authService: AuthenticationService;
let fixtureCustom: ComponentFixture;
let elementCustom: HTMLElement;
let componentCustom: SimpleSearchTestCustomEmptyComponent;
let searchServiceSpy: any;
setupTestBed({
imports: [
NoopAnimationsModule,
CoreModule.forRoot()
],
declarations: [
SearchControlComponent,
SearchComponent,
SearchTriggerDirective,
EmptySearchResultComponent,
SimpleSearchTestCustomEmptyComponent
],
providers: [
ThumbnailService,
SearchService
]
});
beforeEach(() => {
fixture = TestBed.createComponent(SearchControlComponent);
debugElement = fixture.debugElement;
searchService = TestBed.get(SearchService);
authService = TestBed.get(AuthenticationService);
spyOn(authService, 'isEcmLoggedIn').and.returnValue(true);
component = fixture.componentInstance;
element = fixture.nativeElement;
searchServiceSpy = spyOn(searchService, 'search').and.callThrough();
});
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
function typeWordIntoSearchInput(word: string): void {
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = word;
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
}
describe('when input values are inserted', () => {
beforeEach(() => {
fixture.detectChanges();
});
it('should emit searchChange when search term input changed', async(() => {
searchServiceSpy.and.returnValue(
Observable.of({ entry: { list: [] } })
);
component.searchChange.subscribe(value => {
expect(value).toBe('customSearchTerm');
});
typeWordIntoSearchInput('customSearchTerm');
fixture.detectChanges();
}));
it('should update FAYT search when user inputs a valid term', async(() => {
typeWordIntoSearchInput('customSearchTerm');
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#result_option_0')).not.toBeNull();
expect(element.querySelector('#result_option_1')).not.toBeNull();
expect(element.querySelector('#result_option_2')).not.toBeNull();
});
}));
it('should NOT update FAYT term when user inputs an empty string as search term ', async(() => {
typeWordIntoSearchInput('');
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#result_option_0')).toBeNull();
});
}));
it('should still fire an event when user inputs a search term less than 3 characters', async(() => {
searchServiceSpy.and.returnValue(Observable.of(results));
component.searchChange.subscribe(value => {
expect(value).toBe('cu');
});
fixture.detectChanges();
fixture.whenStable().then(() => {
typeWordIntoSearchInput('cu');
});
}));
});
describe('expandable option false', () => {
beforeEach(() => {
component.expandable = false;
fixture.detectChanges();
});
it('search button should be hide', () => {
let searchButton: any = element.querySelector('#adf-search-button');
expect(searchButton).toBe(null);
});
it('should not have animation', () => {
expect(component.subscriptAnimationState).toBe('no-animation');
});
});
describe('component rendering', () => {
it('should display a text input field by default', async(() => {
fixture.detectChanges();
expect(element.querySelectorAll('input[type="text"]').length).toBe(1);
expect(element.querySelector('#adf-control-input')).toBeDefined();
expect(element.querySelector('#adf-control-input')).not.toBeNull();
}));
it('should set browser autocomplete to off by default', async(() => {
fixture.detectChanges();
let attr = element.querySelectorAll('input[type="text"]')[0].getAttribute('autocomplete');
expect(attr).toBe('off');
}));
it('should display a search input field when specified', async(() => {
component.inputType = 'search';
fixture.detectChanges();
expect(element.querySelectorAll('input[type="search"]').length).toBe(1);
}));
it('should set browser autocomplete to on when configured', async(() => {
component.autocomplete = true;
fixture.detectChanges();
expect(element.querySelectorAll('input[type="text"]')[0].getAttribute('autocomplete')).toBe('on');
}));
xit('should fire a search when a enter key is pressed', (done) => {
component.submit.subscribe((value) => {
expect(value).toBe('TEST');
done();
});
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
let enterKeyEvent: any = new Event('keyup');
enterKeyEvent.keyCode = '13';
inputDebugElement.nativeElement.dispatchEvent(enterKeyEvent);
});
});
describe('autocomplete list', () => {
it('should make autocomplete list control hidden initially', async(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).toBeNull();
}));
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);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: Element = element.querySelector('#autocomplete-search-result-list');
expect(resultElement).not.toBe(null);
done();
});
});
it('should show autocomplete list noe results when search box has focus and there is search result with length 0', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(noResult));
fixture.detectChanges();
typeWordIntoSearchInput('NO RES');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let noResultElement: Element = element.querySelector('#search_no_result');
expect(noResultElement).not.toBe(null);
done();
});
});
it('should hide autocomplete list results when the search box loses focus', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('NO RES');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: Element = element.querySelector('#autocomplete-search-result-list');
expect(resultElement).not.toBe(null);
inputDebugElement.nativeElement.dispatchEvent(new Event('blur'));
fixture.detectChanges();
resultElement = element.querySelector('#autocomplete-search-result-list');
expect(resultElement).not.toBe(null);
done();
});
});
it('should keep autocomplete list control visible when user tabs into results', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: HTMLElement = element.querySelector('#result_option_0');
resultElement.focus();
expect(resultElement).not.toBe(null);
inputDebugElement.nativeElement.dispatchEvent(new KeyboardEvent('keypress', { key: 'TAB' }));
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
done();
});
});
it('should close the autocomplete when user press ESCAPE', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: 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 = element.querySelector('#result_option_0');
expect(resultElement).toBeNull();
done();
});
});
});
it('should close the autocomplete when user press ENTER on input', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let resultElement: 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 = element.querySelector('#result_option_0');
expect(resultElement).toBeNull();
done();
});
});
});
it('should focus input element when autocomplete list is cancelled', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
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(document.activeElement.id).toBe(inputDebugElement.nativeElement.id);
done();
});
});
it('should NOT display a autocomplete list control when configured not to', (done) => {
searchServiceSpy.and.returnValue(Observable.of(results));
component.liveSearchEnabled = false;
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).toBeNull();
done();
});
});
it('should select the first item on autocomplete list when ARROW DOWN is pressed on input', (done) => {
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
fixture.detectChanges();
expect(document.activeElement.id).toBe('result_option_0');
done();
});
});
it('should select the second item on autocomplete list when ARROW DOWN is pressed on list', (done) => {
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
fixture.detectChanges();
expect(document.activeElement.id).toBe('result_option_0');
let firstElement = debugElement.query(By.css('#result_option_0'));
firstElement.triggerEventHandler('keyup.arrowdown', { target: firstElement.nativeElement });
fixture.detectChanges();
expect(document.activeElement.id).toBe('result_option_1');
done();
});
});
it('should focus the input search when ARROW UP is pressed on the first list item', (done) => {
searchServiceSpy.and.returnValue(Observable.of(results));
fixture.detectChanges();
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(element.querySelector('#autocomplete-search-result-list')).not.toBeNull();
inputDebugElement.triggerEventHandler('keyup.arrowdown', {});
fixture.detectChanges();
expect(document.activeElement.id).toBe('result_option_0');
let firstElement = debugElement.query(By.css('#result_option_0'));
firstElement.triggerEventHandler('keyup.arrowup', { target: firstElement.nativeElement });
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(document.activeElement.id).toBe('adf-control-input');
done();
});
});
});
});
describe('search button', () => {
it('should NOT display a autocomplete list control when configured not to', fakeAsync(() => {
fixture.detectChanges();
tick(100);
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('active');
searchButton.triggerEventHandler('click', null);
fixture.detectChanges();
tick(100);
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('inactive');
discardPeriodicTasks();
}));
it('click on the search button should open the input box when is close', fakeAsync(() => {
fixture.detectChanges();
tick(100);
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
searchButton.triggerEventHandler('click', null);
tick(100);
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('active');
discardPeriodicTasks();
}));
xit('click on the search button should apply focus on input', fakeAsync(() => {
fixture.detectChanges();
tick(100);
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
searchButton.triggerEventHandler('click', null);
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
tick(100);
fixture.detectChanges();
tick(100);
expect(document.activeElement.id).toBe(inputDebugElement.nativeElement.id);
discardPeriodicTasks();
}));
it('Search button should not change the input state too often', fakeAsync(() => {
fixture.detectChanges();
tick(100);
let searchButton: DebugElement = debugElement.query(By.css('#adf-search-button'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('active');
searchButton.triggerEventHandler('click', null);
fixture.detectChanges();
tick(100);
searchButton.triggerEventHandler('click', null);
fixture.detectChanges();
tick(100);
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('inactive');
discardPeriodicTasks();
}));
it('Search bar should close when user press ESC button', fakeAsync(() => {
fixture.detectChanges();
tick(100);
let inputDebugElement = debugElement.query(By.css('#adf-control-input'));
component.subscriptAnimationState = 'active';
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('active');
inputDebugElement.triggerEventHandler('keyup.escape', {});
tick(100);
fixture.detectChanges();
tick(100);
expect(component.subscriptAnimationState).toBe('inactive');
discardPeriodicTasks();
}));
});
describe('option click', () => {
it('should emit a option clicked event when item is clicked', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
expect(item.entry.id).toBe('123');
done();
});
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.nativeElement.click();
});
});
it('should set deactivate the search after element is clicked', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
expect(component.subscriptAnimationState).toBe('inactive');
done();
});
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.nativeElement.click();
});
});
it('should NOT reset the search term after element is clicked', (done) => {
spyOn(component, 'isSearchBarActive').and.returnValue(true);
searchServiceSpy.and.returnValue(Observable.of(results));
component.optionClicked.subscribe((item) => {
expect(component.searchTerm).not.toBeFalsy();
expect(component.searchTerm).toBe('TEST');
done();
});
fixture.detectChanges();
typeWordIntoSearchInput('TEST');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let firstOption: DebugElement = debugElement.query(By.css('#result_name_0'));
firstOption.nativeElement.click();
});
});
});
describe('SearchControlComponent - No result custom', () => {
beforeEach(() => {
fixtureCustom = TestBed.createComponent(SimpleSearchTestCustomEmptyComponent);
componentCustom = fixtureCustom.componentInstance;
elementCustom = fixtureCustom.nativeElement;
});
it('should display the custom no results when it is configured', (done) => {
const noResultCustomMessage = 'BANDI IS NOTHING';
spyOn(componentCustom.searchComponent, 'isSearchBarActive').and.returnValue(true);
componentCustom.setCustomMessageForNoResult(noResultCustomMessage);
searchServiceSpy.and.returnValue(Observable.of(noResult));
fixtureCustom.detectChanges();
let inputDebugElement = fixtureCustom.debugElement.query(By.css('#adf-control-input'));
inputDebugElement.nativeElement.value = 'SOMETHING';
inputDebugElement.nativeElement.focus();
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
fixtureCustom.detectChanges();
fixtureCustom.whenStable().then(() => {
fixtureCustom.detectChanges();
expect(elementCustom.querySelector('#custom-no-result').textContent).toBe(noResultCustomMessage);
done();
});
});
});
});