mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-19 17:14:57 +00:00
Allow use of arrow keys to navigate FAYT results and update tests
Refs #371
This commit is contained in:
parent
68fc93ecbc
commit
a8bea1800b
@ -15,25 +15,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const entryItem = {
|
||||
entry: {
|
||||
id: '123',
|
||||
name: 'MyDoc',
|
||||
isFile : true,
|
||||
content: {
|
||||
mimeType: 'text/plain'
|
||||
},
|
||||
createdByUser: {
|
||||
displayName: 'John Doe'
|
||||
},
|
||||
modifiedByUser: {
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export var result = {
|
||||
list: {
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: '123',
|
||||
name: 'MyDoc',
|
||||
isFile : true,
|
||||
content: {
|
||||
mimeType: 'text/plain'
|
||||
},
|
||||
createdByUser: {
|
||||
displayName: 'John Doe'
|
||||
},
|
||||
modifiedByUser: {
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
}
|
||||
}
|
||||
entryItem
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export var results = {
|
||||
list: {
|
||||
entries: [
|
||||
entryItem,
|
||||
entryItem,
|
||||
entryItem
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -1,8 +1,14 @@
|
||||
<table data-automation-id="autocomplete_results" *ngIf="results && results.length && searchTerm"
|
||||
class="mdl-data-table mdl-js-data-table mdl-shadow--2dp full-width">
|
||||
<tbody>
|
||||
<tr id="result_row_{{idx}}" *ngFor="let result of results; let idx = index" tabindex="0" (blur)="onRowBlur($event)" (focus)="onRowFocus($event)" (click)="onItemClick(result, $event)" (keyup.enter)="onRowEnter(result, $event)"
|
||||
attr.data-automation-id="autocomplete_result_for_{{result.entry.name}}">
|
||||
<tbody #resultsTableBody>
|
||||
<tr id="result_row_{{idx}}" *ngFor="let result of results; let idx = index" tabindex="0"
|
||||
(blur)="onRowBlur($event)" (focus)="onRowFocus($event)"
|
||||
(click)="onItemClick(result)"
|
||||
(keyup.enter)="onRowEnter(result)"
|
||||
(keyup.arrowdown)="onRowArrowDown($event)"
|
||||
(keyup.arrowup)="onRowArrowUp($event)"
|
||||
(keyup.escape)="onRowEscape($event)"
|
||||
attr.data-automation-id="autocomplete_result_for_{{result.entry.name}}">
|
||||
<td class="img-td"><img src="{{getMimeTypeIcon(result)}}" alt="{{getMimeTypeKey(result)|translate}}"/></td>
|
||||
<td>
|
||||
<div id="result_name_{{idx}}" class="truncate"><b>{{result.entry.name}}</b></div>
|
||||
|
@ -19,7 +19,7 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { AlfrescoSearchAutocompleteComponent } from './alfresco-search-autocomplete.component';
|
||||
import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service';
|
||||
import { TranslationMock } from './../assets/translation.service.mock';
|
||||
import { result, folderResult, noResult, errorJson } from './../assets/alfresco-search.component.mock';
|
||||
import { result, results, folderResult, noResult, errorJson } from './../assets/alfresco-search.component.mock';
|
||||
import { AlfrescoSearchService } from '../services/alfresco-search.service';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
@ -35,6 +35,12 @@ describe('AlfrescoSearchAutocompleteComponent', () => {
|
||||
let fixture: ComponentFixture<AlfrescoSearchAutocompleteComponent>, element: HTMLElement;
|
||||
let component: AlfrescoSearchAutocompleteComponent;
|
||||
|
||||
let updateSearchTerm = (newSearchTerm: string): void => {
|
||||
let oldSearchTerm = component.searchTerm;
|
||||
component.searchTerm = newSearchTerm;
|
||||
component.ngOnChanges({searchTerm: { currentValue: newSearchTerm, previousValue: oldSearchTerm}});
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@ -64,230 +70,337 @@ describe('AlfrescoSearchAutocompleteComponent', () => {
|
||||
expect(translationService.addTranslationFolder).toHaveBeenCalledWith('node_modules/ng2-alfresco-search/dist/src');
|
||||
});
|
||||
|
||||
it('should display search results when a search term is provided', () => {
|
||||
let searchTerm = { currentValue: 'customSearchTerm', previousValue: ''};
|
||||
spyOn(component, 'displaySearchResults').and.stub();
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({
|
||||
searchTerm: searchTerm
|
||||
describe('search results', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
expect(component.displaySearchResults).toHaveBeenCalledWith(searchTerm.currentValue);
|
||||
|
||||
it('should clear results straight away when a new search term is entered', async(() => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''} });
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('table[data-automation-id="autocomplete_results"] tbody tr').length).toBe(0);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display the returned search results', (done) => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect( element.querySelector('#result_user_0').innerHTML).toBe('John Doe');
|
||||
expect( element.querySelector('#result_name_0').innerHTML).toBe('<b _ngcontent-a-1="">MyDoc</b>');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should display the correct thumbnail for result items', (done) => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.baseComponentPath = 'http://localhost';
|
||||
|
||||
let thumbnailService = fixture.debugElement.injector.get(AlfrescoThumbnailService);
|
||||
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('fake-type-icon.svg');
|
||||
spyOn(thumbnailService, 'getMimeTypeKey').and.returnValue('FAKE_TYPE');
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let imgEl = <any> element.querySelector('#result_row_0 img');
|
||||
expect(imgEl).not.toBeNull();
|
||||
expect(imgEl.src).toBe('http://localhost/img/fake-type-icon.svg');
|
||||
expect(imgEl.alt).toBe('SEARCH.ICONS.FAKE_TYPE');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should display no result if no result are returned', (done) => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(noResult));
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#search_no_result')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should clear results straight away when a new search term is entered', async(() => {
|
||||
describe('errors', () => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
let searchService;
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''} });
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('table[data-automation-id="autocomplete_results"] tbody tr').length).toBe(0);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display the returned search results', (done) => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
expect( element.querySelector('#result_user_0').innerHTML).toBe('John Doe');
|
||||
expect( element.querySelector('#result_name_0').innerHTML).toBe('<b _ngcontent-a-1="">MyDoc</b>');
|
||||
done();
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.reject(errorJson));
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''} });
|
||||
it('should display an error if an error is encountered running the search', (done) => {
|
||||
|
||||
component.errorEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let resultsEl = element.querySelector('[data-automation-id="autocomplete_results"]');
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(resultsEl).toBeNull();
|
||||
expect(errorEl).not.toBeNull();
|
||||
expect(errorEl.innerText.trim()).toBe('SEARCH.RESULTS.ERROR');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should clear errors straight away when a new search is performed', async(() => {
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(errorEl).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should display the correct thumbnail for result items', (done) => {
|
||||
describe('mouse interactions', () => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
let searchService;
|
||||
|
||||
component.baseComponentPath = 'http://localhost';
|
||||
|
||||
let thumbnailService = fixture.debugElement.injector.get(AlfrescoThumbnailService);
|
||||
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('fake-type-icon.svg');
|
||||
spyOn(thumbnailService, 'getMimeTypeKey').and.returnValue('FAKE_TYPE');
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let imgEl = <any> element.querySelector('#result_row_0 img');
|
||||
expect(imgEl).not.toBeNull();
|
||||
expect(imgEl.src).toBe('http://localhost/img/fake-type-icon.svg');
|
||||
expect(imgEl.alt).toBe('SEARCH.ICONS.FAKE_TYPE');
|
||||
done();
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
});
|
||||
|
||||
it('should emit preview when file item clicked', (done) => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.preview.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not emit preview if a non-file item is clicked', (done) => {
|
||||
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(folderResult));
|
||||
|
||||
spyOn(component.preview, 'emit');
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
expect(component.preview.emit).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''} });
|
||||
});
|
||||
|
||||
it('should display no result if no result are returned', (done) => {
|
||||
describe('keyboard interactions', () => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(noResult));
|
||||
let searchService;
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#search_no_result')).not.toBeNull();
|
||||
done();
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
});
|
||||
|
||||
it('should emit preview when enter key pressed when a file item is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Enter'
|
||||
}));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.preview.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit cancel event when escape key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Escape'
|
||||
}));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.cancelEmitter.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should focus the next result when down arrow key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
let secondResult: any = element.querySelector('#result_row_1');
|
||||
spyOn(secondResult, 'focus');
|
||||
firstResult.focus();
|
||||
firstResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
expect(secondResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should do nothing when down arrow key pressed when the last result is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let lastResult: any = element.querySelector('#result_row_2');
|
||||
lastResult.focus();
|
||||
lastResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should focus the previous result when up arrow key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
let secondResult: any = element.querySelector('#result_row_1');
|
||||
spyOn(firstResult, 'focus');
|
||||
secondResult.focus();
|
||||
secondResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
expect(firstResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should emit scroll back event when up arrow key pressed and the first result is in focus', (done) => {
|
||||
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
firstResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
});
|
||||
|
||||
component.scrollBackEmitter.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
});
|
||||
|
||||
it('should display an error if an error is encountered running the search', (done) => {
|
||||
describe('changing focus', () => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.reject(errorJson));
|
||||
let searchService;
|
||||
|
||||
component.errorEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let resultsEl = element.querySelector('[data-automation-id="autocomplete_results"]');
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(resultsEl).toBeNull();
|
||||
expect(errorEl).not.toBeNull();
|
||||
expect(errorEl.innerText.trim()).toBe('SEARCH.RESULTS.ERROR');
|
||||
done();
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
});
|
||||
it('should emit a focus event when a result comes into focus', (done) => {
|
||||
|
||||
it('should clear errors straight away when a new search is performed', async(() => {
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('focus'));
|
||||
});
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.reject(errorJson));
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(errorEl).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit preview when file item clicked', (done) => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
component.focusEmitter.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('focus');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
it('should emit a focus event when a result loses focus', (done) => {
|
||||
|
||||
component.preview.subscribe(e => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('blur'));
|
||||
});
|
||||
|
||||
it('should not emit preview if a non-file item is clicked', (done) => {
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(folderResult));
|
||||
|
||||
spyOn(component.preview, 'emit');
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
expect(component.preview.emit).not.toHaveBeenCalled();
|
||||
done();
|
||||
component.focusEmitter.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('blur');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
});
|
||||
it('should give focus to the first result when focusResult() is called externally', (done) => {
|
||||
|
||||
it('should emit preview when enter key pressed when a file item is in focus', (done) => {
|
||||
component.resultsEmitter.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
spyOn(firstResult, 'focus');
|
||||
component.focusResult();
|
||||
expect(firstResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Enter'
|
||||
}));
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
|
||||
component.preview.subscribe(e => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a focus event when a result comes into focus', (done) => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('focus'));
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
|
||||
component.focusEmitter.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('focus');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a focus event when a result loses focus', (done) => {
|
||||
|
||||
let searchService = fixture.debugElement.injector.get(AlfrescoSearchService);
|
||||
spyOn(searchService, 'getSearchNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsEmitter.subscribe(x => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('blur'));
|
||||
});
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''}});
|
||||
|
||||
component.focusEmitter.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('blur');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnInit, OnChanges, Output } from '@angular/core';
|
||||
import { Component, ElementRef, EventEmitter, Input, OnInit, OnChanges, Output, ViewChild } from '@angular/core';
|
||||
import { AlfrescoSearchService } from './../services/alfresco-search.service';
|
||||
import { AlfrescoThumbnailService } from './../services/alfresco-thumbnail.service';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
@ -46,12 +46,20 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges {
|
||||
@Output()
|
||||
focusEmitter: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
|
||||
|
||||
@Output()
|
||||
cancelEmitter = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
resultsEmitter = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
scrollBackEmitter = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
errorEmitter = new EventEmitter();
|
||||
|
||||
@ViewChild('resultsTableBody', {}) resultsTableBody: ElementRef;
|
||||
|
||||
constructor(private alfrescoSearchService: AlfrescoSearchService,
|
||||
private translate: AlfrescoTranslationService,
|
||||
private alfrescoThumbnailService: AlfrescoThumbnailService) {
|
||||
@ -119,10 +127,12 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
onItemClick(node, event?: Event): void {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
focusResult(): void {
|
||||
let firstResult = this.resultsTableBody.nativeElement.querySelector('tr');
|
||||
(<any> firstResult).focus();
|
||||
}
|
||||
|
||||
onItemClick(node): void {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFile) {
|
||||
this.preview.emit({
|
||||
@ -150,4 +160,32 @@ export class AlfrescoSearchAutocompleteComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
return node.nextElementSibling;
|
||||
}
|
||||
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
|
||||
onRowArrowDown($event: KeyboardEvent): void {
|
||||
let nextElement = this.getNextElementSibling(<Element> $event.target);
|
||||
if (nextElement) {
|
||||
(<any> nextElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowUp($event: KeyboardEvent): void {
|
||||
let previousElement = this.getPreviousElementSibling(<Element> $event.target);
|
||||
if (previousElement) {
|
||||
(<any> previousElement).focus();
|
||||
} else {
|
||||
this.scrollBackEmitter.emit($event);
|
||||
}
|
||||
}
|
||||
|
||||
onRowEscape($event: KeyboardEvent): void {
|
||||
this.cancelEmitter.emit($event);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<alfresco-search-autocomplete *ngIf="autocompleteEnabled"
|
||||
<alfresco-search-autocomplete #autocomplete *ngIf="autocompleteEnabled"
|
||||
[searchTerm]="autocompleteSearchTerm" [ngClass]="{active: searchActive, valid: searchValid}"
|
||||
(preview)="onFileClicked($event)"
|
||||
(focusEmitter)="onAutoCompleteFocus($event)"></alfresco-search-autocomplete>
|
||||
(focusEmitter)="onAutoCompleteFocus($event)"
|
||||
(scrollBackEmitter)="onAutoCompleteReturn($event)"
|
||||
(cancelEmitter)="onAutoCompleteCancel($event)"></alfresco-search-autocomplete>
|
||||
|
@ -217,6 +217,42 @@ describe('AlfrescoSearchControlComponent', () => {
|
||||
expect(autocomplete.classList.contains('active')).toBe(true);
|
||||
});
|
||||
|
||||
it('should select the first result in find-as-you-type when down arrow is pressed and FAYT is visible', (done) => {
|
||||
fixture.detectChanges();
|
||||
spyOn(component.autocompleteComponent, 'focusResult');
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new Event('focus'));
|
||||
window.setTimeout(() => { // wait for debounce() to complete
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
fixture.detectChanges();
|
||||
expect(component.autocompleteComponent.focusResult).toHaveBeenCalled();
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should focus input element when find-as-you-type returns control', () => {
|
||||
fixture.detectChanges();
|
||||
spyOn(inputEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
component.onAutoCompleteReturn(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
expect(inputEl.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should focus input element when find-as-you-type is cancelled', () => {
|
||||
fixture.detectChanges();
|
||||
spyOn(inputEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
component.onAutoCompleteCancel(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
expect(inputEl.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT display a find-as-you-type control when configured not to', () => {
|
||||
fixture.componentInstance.autocompleteEnabled = false;
|
||||
fixture.detectChanges();
|
||||
|
@ -18,6 +18,7 @@
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ViewChild } from '@angular/core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { AlfrescoSearchAutocompleteComponent } from './alfresco-search-autocomplete.component';
|
||||
import { SearchTermValidator } from './../forms/search-term-validator';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
|
||||
@ -57,6 +58,9 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('searchInput', {}) searchInput: ElementRef;
|
||||
|
||||
@ViewChild('autocomplete')
|
||||
autocompleteComponent: AlfrescoSearchAutocompleteComponent;
|
||||
|
||||
@Input()
|
||||
autocompleteEnabled = true;
|
||||
|
||||
@ -94,7 +98,7 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private onSearchTermChange(value: string): void {
|
||||
this.searchActive = true;
|
||||
this.setAutoCompleteDisplayed(true);
|
||||
this.autocompleteSearchTerm = value;
|
||||
this.searchControl.setValue(value, true);
|
||||
this.searchValid = this.searchControl.valid;
|
||||
@ -145,6 +149,14 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
isAutoCompleteDisplayed(): boolean {
|
||||
return this.searchActive;
|
||||
}
|
||||
|
||||
setAutoCompleteDisplayed(display: boolean): void {
|
||||
this.searchActive = display;
|
||||
}
|
||||
|
||||
onFileClicked(event): void {
|
||||
this.preview.emit({
|
||||
value: event.value
|
||||
@ -152,11 +164,11 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onSearchFocus($event): void {
|
||||
this.searchActive = true;
|
||||
this.setAutoCompleteDisplayed(true);
|
||||
}
|
||||
|
||||
onSearchBlur($event): void {
|
||||
this.searchActive = false;
|
||||
this.setAutoCompleteDisplayed(false);
|
||||
}
|
||||
|
||||
onFocus($event): void {
|
||||
@ -178,15 +190,32 @@ export class AlfrescoSearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onEscape(): void {
|
||||
this.searchActive = false;
|
||||
this.setAutoCompleteDisplayed(false);
|
||||
}
|
||||
|
||||
onArrowDown(): void {
|
||||
this.searchActive = true;
|
||||
if (this.isAutoCompleteDisplayed()) {
|
||||
this.autocompleteComponent.focusResult();
|
||||
} else {
|
||||
this.setAutoCompleteDisplayed(true);
|
||||
}
|
||||
}
|
||||
|
||||
onAutoCompleteFocus($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onAutoCompleteReturn($event): void {
|
||||
if (this.searchInput) {
|
||||
(<any> this.searchInput.nativeElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onAutoCompleteCancel($event): void {
|
||||
if (this.searchInput) {
|
||||
(<any> this.searchInput.nativeElement).focus();
|
||||
}
|
||||
this.setAutoCompleteDisplayed(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user